/*
    Large less
        programed by M.Suzuki
        ver 0.1     2008/8/4

    key binding
        n,j,^n      scroll up
        p,k,^p      scroll down
        /           i-search
        ESC         i-search cancel
        TAB         next search(i-search only)
        q           quit
 */

#include <stdio.h>
#include <string.h>
#include <curses.h>
#include <pthread.h>

#define LINE_MAX    256     /* file text width max  */

#define False   0
#define True    (!False)

static pthread_mutex_t file_mutex;
static FILE* fileFp;
static FILE* seekFp;
static fpos_t lineTop;
static fpos_t lineMax;
static bool readMaxFlag;
static bool abortFlag;

static void ScanWait()
{
    pthread_mutex_lock(&file_mutex);
    while( lineTop + LINES >= lineMax ){
        static struct timespec time10ms = {0,10*1000*1000};
        pthread_mutex_unlock(&file_mutex);
        nanosleep(&time10ms,NULL);
        pthread_mutex_lock(&file_mutex);
        if( readMaxFlag ){
            break;
        }
    }   /* end of while */
    pthread_mutex_unlock(&file_mutex);
}

static char* ReadLine(int y, char* buff)
{
    char* text = NULL;

    ScanWait();
    pthread_mutex_lock(&file_mutex);
    if( lineTop + y + 1 < lineMax ){
        fpos_t offset = sizeof(fpos_t)*(lineTop+y);

        fsetpos(seekFp,&offset);
        fread(&offset,sizeof(fpos_t),1,seekFp);
        fsetpos(fileFp,&offset);
        if( fgets(buff,LINE_MAX,fileFp) ){
            text = buff;
        }
    }
    pthread_mutex_unlock(&file_mutex);
    return text;
}

static void DrawLine(int y)
{
    char* text;
    char buff[LINE_MAX+1];

    text = ReadLine(y,buff);
    if( text == NULL ){
        text = "~";
    }
    mvinsstr(y,0,text);
}

static void ViewAll()
{
    int y;

    erase();
    for(y=0;y<LINES;y++){
        DrawLine(y);
    }
}

static void RollUp()
{
    ScanWait();
    pthread_mutex_lock(&file_mutex);
    if( lineTop >= lineMax ){
        if( readMaxFlag ){
            pthread_mutex_unlock(&file_mutex);
            return;
        }
    }
    lineTop++;
    pthread_mutex_unlock(&file_mutex);
    move(0,0);
    deleteln();
    move(LINES-1,0);
    DrawLine(LINES-1);
    refresh();
}

static void RollDown()
{
    pthread_mutex_lock(&file_mutex);
    if( lineTop <= 0 ){
        pthread_mutex_unlock(&file_mutex);
        return;
    }
    lineTop--;
    pthread_mutex_unlock(&file_mutex);
    move(0,0);
    insdelln(1);
    DrawLine(0);
    refresh();
}

static void Search()
{
    char search[LINE_MAX];
    int len = 0;

    while(1){
        int key = getch();
        int y = 0;
        if( key == 0x1b ){
            break;
        }
        if( key == '\t' ){
            y = 1;
        } else {
            if( len < LINE_MAX ){
                search[len++] = key;
                search[len] = '\0';
            }
        }
        while(1){
            char buff[LINE_MAX+1];
            if( ReadLine(y,buff) == NULL ){
                return;
            }
            if( strstr(buff,search) ){
                pthread_mutex_lock(&file_mutex);
                lineTop += y;
                pthread_mutex_unlock(&file_mutex);
                ViewAll();
                break;
            }
            y++;
        }   /* end of while */
    }   /* end of while */
}

static void KeyLoop()
{
    ViewAll();
    while(1){
        int key = getch();
        if( key == 'q' ){
            break;
        }
        switch(key){
          case 'N'-'@':
          case 'n':
          case 'j':
            RollUp();
            break;
          case 'P'-'@':
          case 'p':
          case 'k':
            RollDown();
            break;
          case '/':
            Search();
            break;
        }   /* end of switch */
    }   /* end of while */
}

void* ScanThread(void* arg)
{
    fpos_t filePos;

    fgetpos(fileFp, &filePos);
    while(1){
        char buff[LINE_MAX+1];
        fpos_t fpos = sizeof(fpos_t)*lineMax;

        pthread_mutex_lock(&file_mutex);
        fsetpos(seekFp,&fpos);
        fwrite(&filePos,sizeof(fpos_t),1,seekFp);
        lineMax++;
        fsetpos(fileFp,&filePos);
        if( fgets(buff,LINE_MAX,fileFp) == NULL ){
            break;
        }
        fgetpos(fileFp,&filePos);
        if( abortFlag ){
            break;
        }
        pthread_mutex_unlock(&file_mutex);
    }   /* end of while */
    readMaxFlag = True;
    pthread_mutex_unlock(&file_mutex);
    return NULL;
}

static void MainLoop()
{
    pthread_t scanThread_id;
    char tmpName[L_tmpnam];

    tmpnam(tmpName);
    if( (seekFp=fopen(tmpName,"w+b")) == NULL ){
        perror(tmpName);
        return;
    }

    pthread_mutex_init(&file_mutex,NULL);

    lineTop = 0;
    lineMax = 0;
    if( pthread_create(&scanThread_id,NULL,ScanThread,NULL)!=0){
        perror("ScanThread");
        return;
    }

    initscr();
    noecho();
    raw();
    cbreak();

    KeyLoop();

    nocbreak();
    noraw();
    echo();
    endwin();

    pthread_mutex_lock(&file_mutex);
    abortFlag = True;
    pthread_mutex_unlock(&file_mutex);
    pthread_join(scanThread_id,NULL);

    fclose(seekFp);
    remove(tmpName);
}

int main(int argc, char* argv[])
{
    char* fname = NULL;
    int i;

    for(i=1;i<argc;i++){
        char* p = argv[i];
        if( *p == '-' ){
            /* option   */
        } else {
            fname = argv[i];
        }
    }   /* end of for */
    if( fname != NULL ){
        if( (fileFp=fopen(fname,"r")) == NULL ){
            perror(fname);
            return 1;
        }
        MainLoop();
        fclose(fileFp);
    }
    return 0;
}
