/*
\\              Support functions for Editor Class
*/

        #include "Editor.h"
        #include <math.h>
                
struct LineData *FindVisLoc( struct InstanceData *Data, LONG line )
{
        /* Find location of line Y, counting from data->topvisline, or start/end */
        
        struct linedata *res;
        LONG count = MAXINT;
        BOOL dir = TRUE;

        if( (line < 0) || (line >= data->lines) )
                return( NULL );

        if( abs( line ) < abs(count) )                                  /* Right at start */
        {
                count = line;
                res = firstline( data );
        }
        if( (abs( line - data->topvisy ) < abs( count )) &&
                        data->topvisloc )                                                                               /* Top of visible area */
        {
                count = line - data->topvisy;
                res = data->topvisloc;
        }
        if( abs( line - data->lines + 1 ) < abs( count ) )      /* Right at bottom */
        {
                count = line - data->lines + 1;
                res = lastline( data );
        }
        if( count < 0 )
        {
                dir = FALSE;
                count = -count;
        }
        for( ; res && count; )
        {
                res = nextline( data, res, dir );
                if( res && (res->len != -1) ) count--;
        }

        return( res );
}

ULONG calclines( UBYTE *buffer, ULONG bufflen, BOOL partlines )
{
        /* Number of lines in buffer */

        ULONG lines = partlines?1:0;

        for( ; bufflen; bufflen--, buffer++ )
                if( *buffer == '\n' ) lines++;

        return( lines );
}

struct linedata *firstline( struct ClassData *data )
{
        if( IsListEmpty( (struct List *) &data->linelist ) )
                return( NULL );
        return( (struct linedata *) data->linelist.mlh_Head );
}

struct linedata *lastline( struct ClassData *data )
{
        if( IsListEmpty( (struct List *) &data->linelist ) )
                return( NULL );
        return( (struct linedata *) data->linelist.mlh_TailPred );
}

struct linedata *nextline( struct ClassData *data, struct linedata *pos, BOOL forwards )
{
        /* Scan for start of next/previous line */

        struct linedata *newpos;

        if( forwards )
        {
                newpos = (struct linedata *) pos->node.mln_Succ;
                return( (newpos->node.mln_Succ && (newpos->len!=-1))?newpos:NULL );
        }
        else {
                newpos = (struct linedata *) pos->node.mln_Pred;
                return( (newpos->node.mln_Pred && (newpos->len!=-1))?newpos:NULL );
        }
}

void GetBuffer( struct InstanceData *Data )
{
        /* Copy current line into linebuffer */

        UBYTE *Start, *End;
        ULONG Length;

        if( Data->curry > Data->Lines )
             set( Data->Obj, MUIA_Editor_CurrY, Data->Lines-1 );

        if( Data->currline = FindVisLoc( Data, Data->curry ) )
        {
                if( (Length = Data->currline->Length) < 1024 )
                {
                        Data->BufferSize = Length;
                        Start = Data->currline->Data;
                        end = Data->LineBuffer;
                        for( ; Length; Length-- )
                                *End++ = *Start++;                      /* Copy the string */
                        for( ; End < (Data->LineBuffer + 1024); End++ )
                                *End = NULL;
                }
                else AlertMessage( "Bad line length in buffer GetBuffer() " );
        }
}

void storebuffer( struct ClassData *data )
{
        /* Copy linebuffer into main buffer */

        struct linedata *line;
        UBYTE *start, *end;
        LONG length;

        line = data->currline;
        if( !line || (line->len != -1) )                        // Does this line still exist?
        {
                if( !line || (data->buffersize != line->len) )  /* Do we need a new linedata struct? */
                {
                        remline( data, data->curry );
                        line = addline( data, data->curry, data->buffersize );
                }
                if( line )
                {
                        end = line->data;
                        start = data->linebuffer;
                        for( length = data->buffersize; length; length-- )
                                *end++ = *start++;
                        data->currline = line;

                        splitline( data, data->curry ); /* In case of newlines... */
                }
        }
        else showmessage( "Attempting to store deleted line (storebuffer)" );
}

UBYTE *xloc( struct ClassData *data, UBYTE *line, ULONG xpos )
{
        /* Returns the character X chars into the line, or closest available */
        return( NULL );
}

ULONG maxx( struct ClassData *data, ULONG line )
{
        /* Returns the highest possible x pos of the specified line */
        
        struct linedata *ld = findvisloc( data, line );
        return( (ULONG) (ld?ld->len:0) );
}

void correctcurrpos( struct ClassData *data, BOOL cursprec )
{
        /* Correct x, y pos and currptr to fit properly on current line */
        /* if cursprec is set, cursor has precedence over vis area */
        /* if FOLLOWCURSOR is set, update topvisx and topvisy */
        /* if FOLLOWVISIBLE is set, update currx and curry */

        LONG xpos;

        data->syssetcount++;
        if( data->curry >= data->lines ) set( data->obj, MUIA_Editor_CurrY, data->lines-1 );
        if( data->currx >= data->buffersize ) set( data->obj, MUIA_Editor_CurrX, data->buffersize );

        xpos = calcxpos( data, data->linebuffer, data->currx );

        if( cursprec && (data->flags & FOLLOWCURSOR) )
        {
                if( xpos <= data->topvisx )
                        set( data->obj, MUIA_Editor_VisibleX, max( 0, xpos - data->colsvisible/4 ) );
                if( xpos >= (data->topvisx+data->colsvisible) )
                        set( data->obj, MUIA_Editor_VisibleX, max( 0, xpos - (3*data->colsvisible)/4) );
                if( data->curry < data->topvisy )
                        set( data->obj, MUIA_Editor_VisibleY, (LONG) data->curry );
                if( data->curry >= (data->topvisy+data->linesvisible) )
                        set( data->obj, MUIA_Editor_VisibleY, min( data->lines-1, data->curry+1-data->linesvisible ) );
        }
        if( (!cursprec) && (data->flags & FOLLOWVISIBLE) )
        {
                if( data->curry < data->topvisy )
                        set( data->obj, MUIA_Editor_CurrY, data->topvisy );
                if( data->curry >= (data->topvisy+data->linesvisible) )
                        set( data->obj, MUIA_Editor_CurrY, data->topvisy + data->linesvisible - 1 );
                if( xpos < data->topvisx )
                        set( data->obj, MUIA_Editor_CurrX, correctxpos( data, data->linebuffer, data->topvisx ) );
                if( xpos >= (data->topvisx+data->colsvisible) )
                        set( data->obj, MUIA_Editor_CurrX, correctxpos( data, data->linebuffer, data->topvisx + data->colsvisible - 1 ) );
        }
        data->syssetcount--;
}

ULONG calcxpos( struct ClassData *data, UBYTE *string, ULONG xpos )
{
        /* Calculate new xpos after tab expansion */

        LONG tabsleft;
        ULONG currpos;
        UBYTE *curr = string;

        if( xpos == 0 ) return( 0 );

        for( currpos = 0; *curr && (*curr != '\n') && xpos; curr++, xpos-- )
        {
                currpos++;
                if( *curr == '\t' )
                {
                        tabsleft = h_tab( data, currpos );
                        while( tabsleft-- > 0 ) currpos++;
                }
        }
        return( currpos );
}

ULONG correctxpos( struct ClassData *data, UBYTE *str, LONG xpos )
{
        /* Calculate `real' xpos ( reverse of calcxpos() ) */

        ULONG currpos, currx;
        UBYTE *ptr = str;

        if( xpos == 0 ) return( 0 );            /* To make life less complicated */

        for( currpos = 0, currx = 0; xpos > currx; ptr++, currpos++ )
        {
                currx++;
                if( *ptr == '\t' )
                        currx += h_tab( data, currx );
        }
        return( currpos );
}

void flipmark( struct ClassData *data, ULONG *msy, ULONG *msx, ULONG *mey, ULONG *mex )
{
        /* Store mark values so lower comes first */

        if( (data->marky < data->curry) || ((data->marky == data->curry) && (data->markx < data->currx)) )
        {
                *msy = data->marky;     *mey = data->curry;     /* Values are suitable */
                *msx = data->markx;     *mex = data->currx;
        }
        else {
                *msy = data->curry;     *mey = data->marky;     /* Reverse values so lower is first */
                *msx = data->currx;     *mex = data->markx;
        }
}

BOOL ismarked( struct ClassData *data, ULONG x, ULONG y )
{
        /* Tests whether specified pos is in the marked range */

        ULONG msy, msx, mey, mex;

        if( data->flags & MARKING )     /* We WOULD look stupid if we forgot this... */
        {
                flipmark( data, &msy, &msx, &mey, &mex );
                if(     ((msy < y) || ((msy == y) && (msx <= x))) &&
                                ((mey > y) || ((mey == y) && (mex >= x))))
                        return( TRUE );
        }
        return( FALSE );
}

void markbounds( struct ClassData *data, ULONG ypos, ULONG *firstx, ULONG *lastx )
{
        /* Gives the mark positions for the specified line */
        /* If marking is not on line at all, firstx will be 0xFFFFFFFF */
        /* If marking goes beyond end of line, lastx will be 0xFFFFFFFF */

        ULONG msy, msx, mey, mex;

        if( data->flags & MARKING )
        {
                flipmark( data, &msy, &msx, &mey, &mex );
                if( (msy > ypos) || (mey < ypos) ) *firstx = -1;        /* Not on line at all */
                else {
                        if( msy < ypos ) *firstx = 0;
                        if( mey > ypos ) *lastx = -1;
                        if( msy == ypos ) *firstx = msx;
                        if( mey == ypos ) *lastx = mex;
                }
        }
        else *firstx = -1;      /* Marking is not set, so... */
}

void UpdateRenderArea( struct InstanceData *data )
{
        /* Update render bitmap to optimum size */

        ULONG depth, width, tmpsize;
        APTR tempptr;

        if( data->font )        /* Not much point if there's no font specified */
        {
                depth = _rp(data->obj)->BitMap->Depth;
                width = _mwidth(data->obj);
                if( (depth > 0) && (width > 0) && (depth <= 24) && (width<=32768) )
                {
                        WaitBlit();
                        if( data->rp.BitMap ) FreeBitMap( data->rp.BitMap );
                        if( data->rptmpras.RasPtr ) FreeVec( data->rptmpras.RasPtr );
                        data->rp.BitMap = AllocBitMap( _mwidth(data->obj), data->font->tf_YSize, _rp(data->obj)->BitMap->Depth, BMF_INTERLEAVED, _rp(data->obj)->BitMap );
                        tmpsize = data->rp.BitMap->BytesPerRow * data->rp.BitMap->Rows;
                        if( tempptr = AllocVec( tmpsize, MEMF_CHIP ) )
                                InitTmpRas( &data->rptmpras, tempptr, tmpsize );
                }
        }
}

struct LineData * AddLine( struct Instance *Data, ULONG AtLine, UWORD Length )
{
        /* Insert a line at line <line> of length <length> */

        struct LineData *NewLine, *Line;

        if( Length > 1023 ) return( NULL );
        if( !(NewLine = AllocPooled( Data->LinePool, Length + sizeof(struct LineData) ) ) )
                return( NULL );
        NewLine->Length = Length;
        if( AtLine )
        {
                if( AtLine == MUIV_Editor_Max )
                    Line = NULL;
                  else
                    Line = findvisloc( Data, AtLine-1 );

                if( Line )
                  Insert( &Data->LineList, NewLine, Line );
                else
                  AddTail( &Data->LineList, NewLine );
        }
        else
         AddHead( &Data->LineList, NewLine );
        Data->Lines++;
        if( Data->oldy <= AtLine )    Data->oldy++;
        if( Data->Flags & ISEMPTY ) set( Data->Obj, MUIA_Editor_IsEmpty, FALSE );
        return( NewLine );
}

void remline( struct ClassData *data, ULONG line )
{
        struct linedata *ld;

        if( ld = findvisloc( data, line ) )
        {
                if( ld->len != -1 )
                {
                        Remove( (struct Node *) ld );
                        FreePooled( data->linepool, ld, ld->len + sizeof(struct linedata) );
                        data->lines--;
                        if( data->oldy >= line ) data->oldy--;
                        if( data->currline == ld ) data->currline = NULL;
                        if( data->topvisloc == ld ) data->topvisloc = NULL;
                        ld->len = (WORD) -1;
                }
                else showmessage( "Bad line in list!" );
        }
}

struct linedata *mergelines( struct ClassData *data, ULONG line1 )
{
        /* Merge line with following... */

        struct linedata *ld1, *ld2, *newld;

        if( (line1 == data->curry) || (line1 == (data->curry+1)) ) storebuffer( data );
        ld1 = findvisloc( data, line1 );
        ld2 = findvisloc( data, line1 + 1 );
        if( !(ld1 && ld2 && (newld = addline( data, line1, ld1->len + ld2->len ) ) ) )
                return( NULL );
        memcpy( newld->data, ld1->data, ld1->len );
        memcpy( newld->data+ld1->len, ld2->data, ld2->len );
        remline( data, line1 + 2);
        remline( data, line1 + 1);
        getbuffer( data );
        data->flags |= ((data->curry == line1)?DRAWREMOVE:DRAWALL);
        return( newld );
}

struct linedata *chopline( struct ClassData *data, ULONG line, ULONG xpos )
{
        /* Cuts line in two, second line starting from xpos */
        
        struct linedata *ld, *ld1, *ld2;

        if( !(ld = findvisloc( data, line ) ) )
                return( NULL );
        if( xpos > ld->len ) xpos = ld->len;

        if(     !(ld1 = addline( data, line + 1, xpos ) ) ||
                        !(ld2 = addline( data, line + 2, ld->len - xpos ) ) )
                return( NULL );
        if( xpos )                              memcpy( ld1->data, ld->data, ld1->len );
        if( ld->len - xpos )    memcpy( ld2->data, ld->data+xpos, ld2->len );
        remline( data, line );
        return( ld1 );
}

struct linedata *splitline( struct ClassData *data, ULONG line )
{
        /* Checks to see if there is a newline character (0x0A) in the line,
                And splits it into two if so.
                If AUTOINDENT is set, leading tabs/spaces get copied also */

        struct linedata *ld, *ld1, *ld2;
        UBYTE *ptr;
        ULONG tabcount, count;
        BOOL leading = TRUE, found = FALSE;

        if( ld = findvisloc( data, line ) )
        {
                for( count = tabcount = 0, ptr = ld->data; (count < ld->len) && !found; count++, ptr++ )
                {
                        if( leading )
                                if( (*ptr == '\t') || (*ptr == ' ') ) tabcount++;
                                else leading = FALSE;
                        if( *ptr == '\n' ) found = TRUE;
                }
                if( found )
                {
                        count--;
                        if( (ld1 = addline( data, line, count )) &&
                                 (ld2 = addline( data, line+1, ld->len - count - 1 + ((data->flags&AUTOINDENT)?tabcount:0) )))
                        {
                                if( count ) memcpy( ld1->data, ld->data, count );
                                if( count < ld->len )
                                if( data->flags & AUTOINDENT )
                                {
                                        memcpy( ld2->data, ld->data, tabcount );
                                        memcpy( ld2->data + tabcount, ld->data+count+1, ld->len-count-1 );
                                }
                                else memcpy( ld2->data, ld->data+count+1, ld->len-count-1);
                                remline( data, line+2 );
                                splitline( data, line+1);       /* Recurse in to handle other newlines... */
                                data->flags |= ((data->curry == line)?DRAWINSERT:DRAWALL);
                                return( ld1 );
                        }
                }
                return( ld );
        }
        return( NULL );
}

void clearbuffer( struct ClassData *data )
{
        /* Clear the buffer completely */

        APTR newpool;
        if( newpool = CreatePool( MEMF_CLEAR|MEMF_PUBLIC, 32768, 32768 ) )      /* Recreate pool */
        {
                if( data->linepool ) DeletePool( data->linepool );      /* Kill everything */
                data->linepool = newpool;                                                                       /* This is the fast way */
                initlist( &data->linelist );                                                            /* Re-initialize line list */
        }
        else while( firstline( data ) ) remline( data, 0 );     /* Can't make new list */

        data->lines = 0;
        data->currline = data->topvisloc = NULL;
        data->flags |= ISEMPTY;
}

ULONG translateyval( struct ClassData *data, ULONG yval )
{
        /* Convert special values of yval to their actual equivalents */

        switch( yval )
        {
                case MUIV_Editor_Max:   yval = data->lines - 1;
                                                                                break;
                case MUIV_Editor_Prev:  yval = data->curry?(data->curry-1):0;
                                                                                break;
                case MUIV_Editor_Next:  yval = data->curry+1;
                                                                                break;
                case MUIV_Editor_CurrPos:
                                                                                yval = data->curry;
                                                                                break;
        }
        yval = min( yval, data->lines - 1 );
        return( yval );
}

ULONG translatexval( struct ClassData *data, ULONG xval, ULONG yval )
{
        /* Translate special values of x to their actual values */
        /* yval == line, or -1 for current */

        struct linedata *ld;

        if( yval != -1 ) ld = findvisloc( data, yval );
        switch( xval )
        {
                case MUIV_Editor_Max:   xval = 1+((yval==-1)?data->buffersize:ld->len);
                                                                                break;
                case MUIV_Editor_Prev:  xval = data->currx?(data->currx-1):0;
                                                                                break;
                case MUIV_Editor_Next:  xval = data->currx + 1;
                                                                                break;
                case MUIV_Editor_CurrPos:
                                                                                xval = data->currx;
                                                                                break;
        }
        xval = min( xval, (yval==-1)?data->buffersize:ld->len );
        return( xval );
}

void NewMinList( struct MinList *ml )
{            // Was initlist()
        ml->mlh_Head            = (struct MinNode *) &ml->mlh_Tail;     /* Head points to tail  */
        ml->mlh_Tail            = NULL;                                                                         /* Tail is always null  */
        ml->mlh_TailPred        = (struct MinNode *) &ml->mlh_Head;     /* TPred points to head */
}

SAVEDS void AlertMessage( UBYTE *str )
{                // was showmessage
        /*      (Fairly safe) routine to pass a message to the user */

        UBYTE buffer[256];
        UBYTE *ptr;

        buffer[0] = 0;
        buffer[1] = 40;
        buffer[2] = 20;
        ptr = stpcpy( buffer+3, str );
        *(ptr+1) = NULL;
        DisplayAlert( RECOVERY_ALERT, buffer, 40 );
}

BOOL SetFont( struct ClassData *data, struct TextAttr *font )
{
        /* Set font to that specified in textattr */
        /* if NULL, use _font(data->obj) */

        struct TextFont *tf;

        if( font )
        {
                if(     !(tf = OpenFont( font ) ) &&
                                !(DiskfontBase && (tf = OpenDiskFont( font ) )) )
                        return( FALSE );                                                /* Couldn't open font... */
        }
        else tf = _font(data->obj);
        if( tf->tf_Flags & FPF_PROPORTIONAL )   /* We can't use a prop. font */
        {
                if( font ) CloseFont( tf );
                return( FALSE );
        }

        if( data->flags & CUSTOMFONT )  /* Do we have an old font to close? */
                CloseFont( data->font );
        data->font = tf;                                                /* Store new font */

        if( font )                                                              /* Set the custom flag as needed */
                data->flags |= CUSTOMFONT;
        else data->flags &= ~CUSTOMFONT;
        return( TRUE );
}

ULONG indent_size( struct ClassData *data )
{
        /* Returns the number of leading tabs/spaces on the current line */

        UBYTE *ptr;
        ULONG count;
        BOOL scanning;
        struct linedata *ld;

        ld = findvisloc( data, data->curry );
        for(    ptr = ld->data, count = 0, scanning = TRUE;
                        (count<=ld->len) && scanning;
                        ptr++ )
                if( (*ptr == ' ') || (*ptr == '\t') ) count++;
                else scanning = FALSE;
        
        return( count );
}

void padline( struct ClassData *data )
{
        /* Pad the line until if fits currx */

        UBYTE *ptr;
        ULONG maxpos;
        
        for(    ptr=data->linebuffer+data->buffersize, maxpos=max(((LONG)data->currx)-1,(LONG)data->buffersize);
                        (data->buffersize < maxpos) && (data->buffersize < 1023); )
        {
                *ptr++ = ' ';
                data->buffersize++;
        }
}

void trimline( struct ClassData *data )
{
        /* Trim trailing spaces of the current line */

        UBYTE *ptr;

        for(    ptr = data->linebuffer + data->buffersize - 1;
                        data->buffersize && ((*ptr == '\t')||(*ptr==' '));
                        ptr-- )
                data->buffersize--;
}
