/* Auto: make
*/

IMPORT struct SnapRsrc *SnapRsrc;

#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;
WORD Pattern[5] = {
    0,
    0x0c3f,     /* Frame ....oo....oooooo */
    0x3333,     /* Char  ..oo..oo..oo..oo */
    0x1f1f,     /* Word  ...ooooo...ooooo */
    0xffff      /* Line  oooooooooooooooo */
};

IMPORT LONG xl; /* leftmost x position */
IMPORT LONG xr; /* rightmost x position */
IMPORT LONG yt; /* topmost y position */
IMPORT LONG yb; /* bottommost y position */
LONG minx; /* left limit */
LONG maxx; /* right limit */
LONG maxy; /* bottom limit */
LONG tl, tr; /* used by findword - left and right edge of word */
WORD fw, fh; /* Font width and height used when drawing the frame */
WORD GZZ;
WORD SBM;

IMPORT 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;
UWORD FontWidth;
UWORD Underscore;
UBYTE FontType;
UBYTE LoChar;
UBYTE HiChar;
UWORD Modulo;
UWORD *CharLoc;
UWORD NoOfChars;
UBYTE *SrcData;
IMPORT UBYTE *CharData;
UBYTE IFlags;

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

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

IMPORT struct Screen *theScreen;
IMPORT struct RastPort rp;
struct Layer *LockedLayer;

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;
    Underscore = font->tf_Baseline + 1;
    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, 256L * 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((char *)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 + fw; NewFrame[1].y = yt - 1;
            NewFrame[2].x = xr + fw; NewFrame[2].y = yb + fh;
            NewFrame[3].x = xl - 1;  NewFrame[3].y = yb + fh;
            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 + fw; NewFrame[1].y = yt - 1;
                NewFrame[2].x = xr + fw; NewFrame[2].y = yb + fh;
                NewFrame[3].x = xl - 1;  NewFrame[3].y = yb + fh;
                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 + fw; NewFrame[1].y = yt - 1;
                NewFrame[2].x = maxx + fw; NewFrame[2].y = yb;
                NewFrame[3].x = xr + fw;   NewFrame[3].y = yb;
                NewFrame[4].x = xr + fw;   NewFrame[4].y = yb + fh;
                NewFrame[5].x = minx - 1;  NewFrame[5].y = yb + fh;
                NewFrame[6].x = minx - 1;  NewFrame[6].y = yt + fh;
                NewFrame[7].x = xl - 1;    NewFrame[7].y = yt + fh;
                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 + fw; NewFrame[1].y = yt - 1;
            NewFrame[2].x = maxx + fw; NewFrame[2].y = yb + fh;
            NewFrame[3].x = minx - 1;  NewFrame[3].y = yb + fh;
            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 -= fw;
        if (tl < minx) {
            break;
        }
    }
    tl += fw;
    tr = mx;
      /* ...and to the right */
    while (!IsSpace(tr, my)) {
        tr += fw;
        if (tr + fw > maxx) {
            break;
        }
    }
    tr -= fw;
    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;
        }
    }
    if (SnapRsrc->CrawlPtrn == 0) {
        Ptrn = Pattern[Unit];
    }
}

/* 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();                 /* 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 - fh == yb) {
        yb += fh;
    }
    if (yt == yb && xl - fw == xr) {
        xr += fw;
    }
    if (xr > maxx) {         /* Check for window bounds */
        xr = maxx;
    }
    if (xl < minx) {         /* Check for window bounds */
        xl = minx;
    }
    if (yb > maxy) {         /* Check for window bounds */
        yb = maxy;
    }
}

/* The actual character snapper. It actually works. :-) */

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

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

      /* Calculate stuff */
    width = maxx - (minx + 1) + fw + fw;  /* Add one for a LF */
    height = yb - yt + fh;
    SnapSize = ((width / fw) + 1) * (height / fh);
    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;
    }
    IFlags = 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 */
    UnlockLayer(LockedLayer);

      /* Clear our work area */
    BltClear((char *)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 += fw, counter++) {
            CopyChar(x, yt, COPY);
            if ((SnapSpace[counter] = interpret(TempRaster)) == 255) {
                SnapSpace[counter] = SnapRsrc->BadChar;  /* Unrecognized */
            }
        }
        if (Unit == UNIT_LINE) {
            while (counter && SnapSpace[counter-1] == ' ') {
                counter--;
            }
        }
    } else { /* Multiple lines */

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

          /* Read first line */
        for (x = xl; x <= maxx; x += fw, counter++) {
            CopyChar(x, yt, COPY);
            if ((SnapSpace[counter] = interpret(TempRaster)) == 255) {
                SnapSpace[counter] = SnapRsrc->BadChar;  /* Unrecognized */
            }
        }
        if (Unit == UNIT_FRAME) {
            SnapSpace[counter++] = 10;
        } else {
            SHORT endspace = (SnapSpace[counter-1] == ' ');
              /* Remove trailing blanks */
            while (counter && SnapSpace[counter-1] == ' ') {
                counter--;
            }
            if (endspace || !(SnapRsrc->flags & JOINLONG)) {
                SnapSpace[counter++] = 10;
            }
        }

          /* If more than two rows - read full middle rows */
        if (yt + fh != yb) {
            for (y = yt + fh; y < yb; y += fh) {
                for (x = minx; x <= maxx; x += fw, counter++) {
                    CopyChar(x, y, COPY);
                    if ((SnapSpace[counter] = interpret(TempRaster)) == 255) {
                        SnapSpace[counter] = SnapRsrc->BadChar;  /* Unrecognized */
                    }
                }
                if (Unit == UNIT_FRAME) {
                    SnapSpace[counter++] = 10;
                } else {
                    SHORT endspace = (SnapSpace[counter-1] == ' ');
                      /* Remove trailing blanks */
                    while (counter && SnapSpace[counter-1] == ' ') {
                        counter--;
                    }
                    if (endspace || !(SnapRsrc->flags & JOINLONG)) {
                        SnapSpace[counter++] = 10;
                    }
                }
            }
        }

          /* Read last line */
        for (x = minx; x <= xr; x += fw, counter++) {
            CopyChar(x, yb, COPY);
            if ((SnapSpace[counter] = interpret(TempRaster)) == 255) {
                SnapSpace[counter] = SnapRsrc->BadChar;  /* Unrecognized */
            }
        }
        /* 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 */
    LockedLayer = window->WLayer;
    LockLayer(0L, LockedLayer);

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

      /* Or his picture */
    SetDrMd(&rp, COMPLEMENT);
    rp.Mask = SnapRsrc->FrameMask;

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

    if (window->Flags & GIMMEZEROZERO) {
        GZZ = 1;
    } else {
        GZZ = 0;
    }
    if (window->Flags & SUPER_BITMAP) {
        SBM = 1;
    } else {
        SBM = 0;
    }

      /* Find a position */
    xl = (GZZ ? window->GZZMouseX : window->MouseX)
      + window->RPort->Layer->Scroll_X;
    yt = (GZZ ? window->GZZMouseY : window->MouseY)
      + window->RPort->Layer->Scroll_Y;

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

      /* Check your position */
    if (xl > (GZZ ? window->GZZWidth : window->Width) ||
      yt > (GZZ ? window->GZZHeight : window->Height)) {
        UnlockLayer(LockedLayer);
        action = noaction;
        return 0;
    }
    IFlags = 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 struct CacheWindow *cw = GetCachedWindow(theScreen, window);

        if (cw) {
            xoff = - ((xl - cw->xoff) % cw->fw);
            yoff = - ((yt - cw->yoff) % cw->fh);
            BltClear((char *)TempRaster, 32L, 0L);
            ClipBlit(&rp, xl + xoff, yt + yoff,
              &TempRp, 0L, 0L, (LONG)FontWidth, (LONG)FontHeight, COPY);
            WaitBlit();
            if (interpret(TempRaster) != 255) {
                goto found;
            }
        }
          /* No cache or cache didn't match */
        xl -= 7;
        yt -= 7;
        BltClear((char *)TempRaster, 32L, 0L);
        xoff = 0;
        while (xoff < (16 - FontWidth)) {
            ClipBlit(&rp, xl + xoff, yt,
              &TempRp, 0L, 0L, (LONG)FontWidth, 16L, COPY);
            WaitBlit();
            yoff = 0;
            while (yoff < (16 - FontHeight)) {
                if (interpret(&TempRaster[yoff]) != 255) {
                    goto found;
                }
                ++yoff;
            }
            ++xoff;
        }

          /* No character found. Back off */
        UnlockLayer(LockedLayer);
        action = noaction;
        return 0;

found:
          /* Ok, now we know where to look for chars.
          ** xoff and yoff is character position within our 16x16 bitmap.
          */
        xl = xl + xoff;         /* Adjust x */
        yt = yt + yoff;         /* Adjust y */

        fw = FontWidth;
        fh = FontHeight;

        {
            SHORT temp = fh;
            while (temp <= fh + 1) {  /* Check for extra pixel row */
                BltClear((char *)TempRaster, 32L, 0L);
                ClipBlit(&rp, xl, yt + temp,
                  &TempRp, 0L, 0L, (LONG)FontWidth, (LONG)FontHeight, COPY);
                WaitBlit();
                if (interpret(TempRaster) != 255) {
                    fh = temp;
                    break;
                }
                ++temp;
            }
        }

          /* Find out offsets within the window */
        xoff = xl % fw;
        yoff = yt % fh;

        if (cw) {
            cw->xoff = xoff;
            cw->yoff = yoff;
            cw->fw = fw;
            cw->fh = fh;
        } else {
            CacheWindow(window, xoff, yoff, fw, fh);
        }
    }

      /* Set bounds */
    minx = xoff;
    maxx = minx +
      (((GZZ ?
        window->GZZWidth :
        window->Width - window->BorderRight
          /* Hack for borderless conman windows */
        + (window->Flags & BORDERLESS && window->Flags & WINDOWSIZING ? 14 : 0))
      - minx - fw) / fw) * fw;
    maxy = ((GZZ ? window->GZZHeight : window->Height) / fh) * fh;

      /* Check bounds */
    if (xl > maxx) {
        UnlockLayer(LockedLayer);
        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 = SnapRsrc->StartUnit;
    Ptrn = (SnapRsrc->CrawlPtrn ? SnapRsrc->CrawlPtrn : Pattern[Unit]);
    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) && (SnapRsrc->CrawlPtrn != 0xffff)) {
            crawl_frame(0L);
        }

        mx = (LONG)(GZZ ? window->GZZMouseX : window->MouseX)
          + window->RPort->Layer->Scroll_X;
        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) % fw);

        my = (LONG)(GZZ ? window->GZZMouseY : window->MouseY)
          + window->RPort->Layer->Scroll_Y;
        if (my < 0) {
            my = 0;
        }
          /* Calculate which row is closest */
        if ((my - yt) < (yb - my)) {
            closey = closetop;
        } else {
            closey = closebottom;
        }
        my = my - ((my - yoff) % fh);

          /* 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();
            UnlockLayer(LockedLayer);
            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();
        }
    }
}

