#include "snap.h"

#define COPY 0xC0L
#define INVCOPY 0x30L
#define CopyChar(_x, _y, _m)                                  \
    BltBitMap(&MyBM, (LONG)_x, (LONG)_y,                      \
      &TempBM, 0L, 0L, (LONG)FontWidth, (LONG)FontHeight,     \
      _m, -1L, NULL);                                         \
    WaitBlit()

WORD Unit;
IMPORT WORD StartUnit;

LONG xl; /* leftmost x position */
LONG xr; /* rightmost x position */
LONG yt; /* topmost y position */
LONG yb; /* bottommost y position */
LONG minx; /* left limit */
LONG maxx; /* right limit */
LONG tl, tr; /* used by findword - left and right edge of word */

LONG mx, my; /* Mouse position in character steps */
#define closetop     0
#define closebottom  1
WORD closey;
#define closeleft   0
#define closeright  1
WORD closex;


struct Window *window;  /* The window we're snapping from */

/* Data for font being snapped */
UWORD FontHeight;
UBYTE FontType;
UWORD FontWidth;
UBYTE LoChar;
UBYTE HiChar;
UWORD Modulo;
UWORD *CharLoc;
UWORD NoOfChars;
UBYTE *SrcData;
IMPORT UBYTE *CharData;

IMPORT struct RastPort TempRp, MyRP;
IMPORT struct BitMap TempBM, MyBM;
IMPORT WORD FrameMask;

IMPORT UWORD *TempRaster;   /* Used for character recognition */

IMPORT struct Screen *theScreen;
IMPORT struct Layer_Info *LockedLayerInfo;
IMPORT struct RastPort rp;
IMPORT UWORD CrawlPtrn;

IMPORT LONGBITS cancelsignal, donesignal, movesignal, clicksignal, timersignal;
IMPORT WORD action;

WORD starting;

/* Init vars with font data.
*/

VOID SetSnapFont(font)
struct TextFont *font;
{
    if (!font) {
        FontWidth = -1;
        return;
    }
    FontHeight = font->tf_YSize;
    FontType = font->tf_Flags;
    FontWidth = (FontType & FPF_PROPORTIONAL ? -1 : font->tf_XSize);
    if (FontWidth == -1) {
        return;
    }
    LoChar = font->tf_LoChar;
    HiChar = font->tf_HiChar;
    Modulo = font->tf_Modulo;
    CharLoc = (UWORD *)font->tf_CharLoc;
    NoOfChars = HiChar-LoChar+1;
    Modulo = font->tf_Modulo;
    SrcData = (UBYTE *)font->tf_CharData;
    BltClear(CharData, 224L*32, 0L);
    WaitBlit();
    CopyFont();
}

/* Check if the character at x, y is a space
*/

WORD IsSpace(x, y)
LONG x, y;
{
    REGISTER WORD i = FontHeight-1;
    REGISTER UWORD *data = &TempRaster[i];

      /* Copy character at x, y */
    BltClear(TempRaster, 32L, 0L);
    ClipBlit(&rp, x, y,
      &TempRp, 0L, 0L, (LONG)FontWidth, (LONG)FontHeight, COPY);
    WaitBlit();

    if (*data) {         /* Try inverted copy */
        ClipBlit(&rp, x, y,
          &TempRp, 0L, 0L, (LONG)FontWidth, (LONG)FontHeight, INVCOPY);
        WaitBlit();
    }
    while (i--) {
        if (*data--) {
            return 0;
        }
    }
    return 1;
}

#define ShortFrame 4L      /* Square frame - columnar select */
#define LongFrame  8L      /* Strange frame - char or word select */
IMPORT LONG OFType;        /* Old frame type: ShortFrame/LongFrame */
IMPORT UWORD Ptrn;
IMPORT Point OldFrame[];
IMPORT Point NewFrame[];


/* update_frame calculates the new frame,
** erases the old frame and draws the new one.
** It's all pretty obvious if you take it slowly.
*/
VOID update_frame()
{
    LONG ft;
    switch (Unit) {
        case UNIT_FRAME: {
              /*********\
              *         *
              *         *
              \*********/
            NewFrame[0].x = xl-1;         NewFrame[0].y = yt-1;
            NewFrame[1].x = xr+FontWidth; NewFrame[1].y = yt-1;
            NewFrame[2].x = xr+FontWidth; NewFrame[2].y = yb+FontHeight;
            NewFrame[3].x = xl-1;         NewFrame[3].y = yb+FontHeight;
            NewFrame[4].x = xl-1;         NewFrame[4].y = yt-1;
            ft = ShortFrame;
            break;
        }
        case UNIT_CHAR:
        case UNIT_WORD: {
            if (yt == yb) {   /* On the same line - same as UNIT_FRAME */
                NewFrame[0].x = xl-1;         NewFrame[0].y = yt-1;
                NewFrame[1].x = xr+FontWidth; NewFrame[1].y = yt-1;
                NewFrame[2].x = xr+FontWidth; NewFrame[2].y = yb+FontHeight;
                NewFrame[3].x = xl-1;         NewFrame[3].y = yb+FontHeight;
                NewFrame[4].x = xl-1;         NewFrame[4].y = yt-1;
                ft = ShortFrame;
            } else {
                      /*****\
                 ******     *
                 *          *
                 *      *****
                 *******/
                NewFrame[0].x = xl-1;           NewFrame[0].y = yt-1;
                NewFrame[1].x = maxx+FontWidth; NewFrame[1].y = yt-1;
                NewFrame[2].x = maxx+FontWidth; NewFrame[2].y = yb;
                NewFrame[3].x = xr+FontWidth;   NewFrame[3].y = yb;
                NewFrame[4].x = xr+FontWidth;   NewFrame[4].y = yb+FontHeight;
                NewFrame[5].x = minx-1;         NewFrame[5].y = yb+FontHeight;
                NewFrame[6].x = minx-1;         NewFrame[6].y = yt+FontHeight;
                NewFrame[7].x = xl-1;           NewFrame[7].y = yt+FontHeight;
                NewFrame[8].x = xl-1;           NewFrame[8].y = yt-1;
                ft = LongFrame;
            }
            break;
        }
        case UNIT_LINE: {
            NewFrame[0].x = minx-1;         NewFrame[0].y = yt-1;
            NewFrame[1].x = maxx+FontWidth; NewFrame[1].y = yt-1;
            NewFrame[2].x = maxx+FontWidth; NewFrame[2].y = yb+FontHeight;
            NewFrame[3].x = minx-1;         NewFrame[3].y = yb+FontHeight;
            NewFrame[4].x = minx-1;         NewFrame[4].y = yt-1;
            ft = ShortFrame;
            break;
        }
        default: {
            break;
        }
    }
    draw_frame(ft);
}

VOID FindWord()
{
      /* Must remove frame to be able to search for spaces */
    WaitTOF();
    erase_frame();
    tl = mx;
      /* Find a space to the left... */
    while (!IsSpace(tl, my)) {
        tl -= FontWidth;
        if (tl < minx) {
            break;
        }
    }
    tl += FontWidth;
    tr = mx;
      /* ...and to the right */
    while (!IsSpace(tr, my)) {
        tr += FontWidth;
        if (tr+FontWidth > maxx) {
            break;
        }
    }
    tr -= FontWidth;
    if (tr < tl) {
        tl = xl;
        tr = xr;
    }
}

/* ChangeUnit cycles the unit of selection. The differents units
   are: character, word and line.
*/

VOID ChangeUnit()
{

    switch (Unit) {
        case UNIT_FRAME: {
            Unit = UNIT_CHAR;
            break;
        }
        case UNIT_CHAR: {
            Unit = UNIT_WORD;
            FindWord();
            xl = tl;
            xr = tr;
            break;
        }
        case UNIT_WORD: {
            Unit = UNIT_LINE;
            xl = minx;
            xr = maxx;
            break;
        }
        case UNIT_LINE: {
            Unit = UNIT_FRAME;
            xl = xr = mx;
            break;
        }
    }
}

/* ExtendSelection extends the current selection according to
   the mouse position and the selected unit.
   Note that ExtendSelection doesn't optimize moves that don't
   make any difference. FIXME
*/

VOID ExtendSelection()
{
    /* Fix which row we're talking about */
    if (closey == closetop) {       /* Find closest row */
        yt = my;               /* change top row */
    } else {
        yb = my;               /* change bottom row */
    }

    /* Take care of left and right character pos */
    switch (Unit) {
        case UNIT_FRAME: {
            if (closex == closeleft) {
                xl = mx;
            } else {
                xr = mx;
            }
            break;
        }
        case UNIT_CHAR: {
            if (yt == yb) {            /* One line */
                if (closex == closeleft) {
                    xl = mx;
                } else {
                    xr = mx;
                }
            } else {                   /* Multiple lines */
                if (yt == my) {
                    xl = mx;           /* At top - set left */
                } else {
                    xr = mx;           /* At bottom - set right */
                }
            }
            break;
        }
        case UNIT_WORD: {
            FindWord(mx, my);          /* Find the word */
            if (yt == yb) {            /* One line */
                if (closex == closeleft) {   /* Find closest char pos */
                    xl = tl;
                } else {
                    xr = tr;
                }
            } else {                   /* Multiple lines */
                if (yt == my) {        /* Where am I */
                    xl = tl;           /* At top - set left */
                } else {
                    xr = tr;           /* At bottom - set right */
                }
            }
            break;
        }
        case UNIT_LINE: {              /* Always full width */
            break;
        }
    }
    if (yt-FontHeight == yb) {
        yb += FontHeight;
    }
    if (yt == yb && xl-FontWidth == xr) {
        xr += FontWidth;
    }
    if (xr > maxx) {         /* Check for window bounds */
        xr = maxx;
    }
    if (xl < minx) {         /* Check for window bounds */
        xl = minx;
    }
}

/* The actual character snapper. It actually works. */

WORD SnapChars()
{
    REGISTER struct RastPort MyRastPort;
    LONG width;
    LONG height;
    UBYTE *SnapSpace;
    ULONG SnapSize;
    ULONG counter;
    REGISTER LONG x, y;

      /* Check coordinates */
    if (yt-FontHeight == yb) {        /* No rows, shouldn't happen */
        return 0;
    }
    if (yt == yb && xl-FontWidth == xr) {     /* Nothing at all */
        return 0;
    }

      /* Calculate stuff */
    width = maxx - (minx+1) + FontWidth+FontWidth;  /* Add one for a LF */
    height = yb - yt + FontHeight;
    SnapSize = ((width/FontWidth)+1) * (height/FontHeight);
    counter = 0;

      /* Initialize things */
    InitRastPort(&MyRP);
    InitBitMap(&MyBM, 1L, width, height);
    MyRP.BitMap = &MyBM;
    SnapSpace = AllocMem(SnapSize, MEMF_PUBLIC|MEMF_CLEAR);
      /* Please insert more memory */
    if (!SnapSpace) {
        return 0;
    }
    MyBM.Planes[0] = AllocRaster(width, height);
    if (!MyBM.Planes[0]) {
        FreeMem(SnapSpace, SnapSize);
        return 0;
    }
      /* Make a local copy of the snapped chars */
    ClipBlit(&rp, minx, yt, &MyRP, 0L, 0L, width, height, COPY);

      /* Ok, now we've got a copy of the character data */
      /* Now it's ok to mess with the layers again */
    UnlockLayers(LockedLayerInfo);

      /* Clear our work area */
    BltClear(TempRaster, 32L, 0L);

      /* Calculate bounds */
    xl -= minx;
    xr -= minx;
    maxx -= minx;
    minx = 0;
    yb -= yt;
    yt = 0;

      /* Single line - needs to be handled separately */
    if (yt == yb) { /* Ok, we've got one */

          /* Read from left to right */
        for (x=xl; x<=xr; x+=FontWidth, counter++) {
            CopyChar(x, yt, COPY);
            if ((SnapSpace[counter] = interpret(TempRaster)) == 255) {
                SnapSpace[counter] = 63;  /* Unrecognized changed to ? */
            }
        }
    } else { /* Multiple lines */

        if (Unit == UNIT_FRAME) {
            minx = xl;
            maxx = xr;
        }

          /* Read first line */
        for (x=xl; x<=maxx; x+=FontWidth, counter++) {
            CopyChar(x, yt, COPY);
            if ((SnapSpace[counter] = interpret(TempRaster)) == 255) {
                SnapSpace[counter] = 63;  /* Unrecognized changed to ? */
            }
        }
        if (Unit == UNIT_FRAME) {
            SnapSpace[counter++] = 10;
        } else {
              /* Remove trailing blanks */
            while (counter && SnapSpace[counter-1] == ' ') {
                counter--;
            }
            SnapSpace[counter++] = 10;
        }

          /* If more than two rows - read full middle rows */
        if (yt+FontHeight != yb) {
            for (y=yt+FontHeight; y<yb; y+=FontHeight) {
                for (x=minx; x<=maxx; x+=FontWidth, counter++) {
                    CopyChar(x, y, COPY);
                    if ((SnapSpace[counter] = interpret(TempRaster)) == 255) {
                        SnapSpace[counter] = 63;  /* Unrecognized - ? */
                    }
                }
                if (Unit == UNIT_FRAME) {
                    SnapSpace[counter++] = 10;
                } else {
                      /* Remove trailing blanks */
                    while (counter && SnapSpace[counter-1] == ' ') {
                        counter--;
                    }
                    SnapSpace[counter++] = 10;
                }
            }
        }

          /* Read last line */
        for (x=minx; x<=xr; x+=FontWidth, counter++) {
            CopyChar(x, yb, COPY);
            if ((SnapSpace[counter] = interpret(TempRaster)) == 255) {
                SnapSpace[counter] = 63;  /* Unrecognized changed to ? */
            }
        }
        /* Remove trailing blanks */
        while (counter && SnapSpace[counter-1] == ' ') {
            counter--;
        }
    }
    FreeRaster(MyBM.Planes[0], width, height);
    SaveClip(SnapSpace, counter);
    FreeMem(SnapSpace, SnapSize);
    return 1;
}


/* HandleChars is the part of the Snap state machine that handles
   snapping of characters. The selection is done in different
   units: char, word, line.
*/

WORD HandleChars()
{
    LONG xoff, yoff;
    LONG ox, oy;

      /* Find out which screen we're working on */
    theScreen = WhichScreen();

      /* Oops, no screen? */
    if (!theScreen) {
        action = noaction;
        return 0;
    }

      /* Ok, what window? */
    window = WhichWindow(theScreen);

      /* Oh dear, no window. */
    if (!window) {
        action = noaction;
        return 0;
    }

      /* No messing with the layers while I think */
    LockedLayerInfo = &window->WScreen->LayerInfo;
    LockLayers(LockedLayerInfo);

      /* Don't want to wreck somebody's rastport */
    CopyMem(window->RPort, &rp, (long)sizeof(struct RastPort));

      /* Or his picture */
    SetDrMd(&rp, COMPLEMENT);
    rp.Mask = FrameMask;
    Ptrn = CrawlPtrn;
    SetDrPt(&rp, Ptrn);

      /* Find out what we're trying to read */
    SetSnapFont(rp.Font);
    if (FontWidth == -1) {
        UnlockLayers(LockedLayerInfo);
        action = noaction;
        return 0;
    }

      /* Find a position */
    xl = window->MouseX - 7;
    yt = window->MouseY - 7;

    if (xl<0) {
        xl = 0;
    }
    if (yt<0) {
        yt = 0;
    }

      /* Check your position */
    if (xl>window->Width || yt>window->Height) {
        UnlockLayers(LockedLayerInfo);
        action = noaction;
        return 0;
    }

      /* Find out the offset for the clicked character, if any.
      ** This is the part that makes it special. Simple, isn't it. Hah!
      */
    {
        REGISTER SHORT found = 0;
        BltClear(TempRaster, 32L, 0L);
        xoff = 0;
        while ((xoff<(16-FontWidth)) && !found) {
            ClipBlit(&rp, xl+xoff, yt,
              &TempRp, 0L, 0L, (LONG)FontWidth, 16L, COPY);
            WaitBlit();
            yoff = 0;
            while ((yoff<(16-FontHeight)) && !found) {
                if (interpret(&TempRaster[yoff]) != 255) {
                    found = 1;
                }
                ++yoff;
            }
            ++xoff;
        }

          /* Did we find a character? */
        if (!found) {
              /* No, back off */
            UnlockLayers(LockedLayerInfo);
            action = noaction;
            return 0;
        }
    }

      /* Ok, now we know where to look for chars.
      ** xoff and yoff is character position within our 16x16 bitmap.
      */
    xl = xl + xoff-1;         /* Adjust x */
    yt = yt + yoff-1;         /* Adjust y */
      /* Find out offsets within the window */
    xoff = xl % FontWidth;
    yoff = yt % FontHeight;
      /* Set bounds */
    minx = xoff;
    maxx = minx+((window->Width-minx-FontWidth
      -(window->Flags & BORDERLESS ? 0 : 2)
      -(window->Flags & WINDOWSIZING ? 12 : 0))/FontWidth)*FontWidth;
      /* Check bounds */
    if (xl>maxx) {
        UnlockLayers(LockedLayerInfo);
        action = noaction;
        return 0;
    }
      /* Set box dimensions */
    xr = xl;
    yb = yt;
    ox = xr;
    oy = yt;

      /* Select unit while starting */
    starting = 1;

      /* Starting unit is character or frame */
    Unit = StartUnit;
    OFType = 0L;
    update_frame();

      /* Get the state machine running */
    FOREVER {
          /* Wait for something to happen */
        REGISTER LONGBITS sig =
          Wait(movesignal|cancelsignal|donesignal|clicksignal|timersignal);

        if ((sig & timersignal) && (CrawlPtrn != 0xffff)) {
            crawl_frame(0L);
        }

        mx = (LONG)window->MouseX;
        if (mx<0) {
            mx = 0;
        }
          /* Calculate which edge is closest */
        if ((mx-xl) < (xr-mx)) {
            closex = closeleft;
        } else {
            closex = closeright;
        }
          /* Only interested in real char pos */
        mx = mx - ((mx-xoff) % FontWidth);

        my = (LONG)window->MouseY;
        if (my<0) {
            my = 0;
        }
          /* Calculate which row is closest */
        if ((my-yt) < (yb-my)) {
            closey = closetop;
        } else {
            closey = closebottom;
        }
        my = my - ((my-yoff) % FontHeight);

          /* Hey, it moves! It's alive!! */
        if ((sig & movesignal) && (action == snaptext)) {
            if (mx != ox || my != oy) {  /* Something's happened */
                ExtendSelection();
                update_frame();
                starting = 0;
                ox = mx;
                oy = my;
                sig &= ~clicksignal;
            }
        }

          /* Ok, forget it... */
        if (sig & cancelsignal) {
            erase_frame();
            UnlockLayers(LockedLayerInfo);
            return 0;
        }

          /* Click */
        if ((sig & clicksignal) && (action == snaptext)) {
              /* Selecting unit */
            if (starting) {
                if (mx == ox && my == oy) {
                    ChangeUnit();
                    if (Unit == UNIT_CHAR) {
                        ChangeUnit();
                    }
                    update_frame();
                } else if (Unit == UNIT_FRAME) {
                    ChangeUnit();
                    update_frame();
                }
            }
            if (mx != ox || my != oy) { /* Click in a new place */
                ExtendSelection();
                update_frame();
                starting = 0;
                ox = mx;
                oy = my;
            }
        }

          /* Finished */
        if (sig & donesignal) {
            erase_frame();
            return SnapChars();
        }
    }
}

