/* 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 */
};

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 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;

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 *CharKern;
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 struct timerequest MyTR;

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

WORD starting;

/* Init vars with font data.
*/

WORD SetSnapFont(font)
struct TextFont *font;
{
    if (!font) {
        return 0;
    }
    FontHeight = font->tf_YSize;
    if (FontHeight > CHEIGHT) {
        return 0;
    }
    Underscore = font->tf_Baseline + 1;
    FontType = font->tf_Flags;
    if (FontType & FPF_PROPORTIONAL) {
        return 0;
    }
    FontWidth = font->tf_XSize;
    LoChar = font->tf_LoChar;
    HiChar = font->tf_HiChar;
    Modulo = font->tf_Modulo;
    CharLoc = (UWORD *)font->tf_CharLoc;
    CharKern = (UWORD *)font->tf_CharKern;
    Modulo = font->tf_Modulo;
    SrcData = (UBYTE *)font->tf_CharData;
    BltClear((char *)CharData, 256L * (CHEIGHT * 2), 0L);
    WaitBlit();
    CopyFont();
    return 1;
}

/* 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, CHEIGHT * 2L, 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;
    REGISTER LONG l, b, r, t, ml, mr;
    l = xl - 1;
    r = xr + fw;
    t = yt - 1;
    b = yb + fh;
    ml = minx - 1;
    mr = maxx + fw;
    if (l < 0)                  l = 0;
    if (r > window->Width - 1)  r = window->Width - 1;
    if (t < 0)                  t = 0;
    if (b > window->Height - 1) b = window->Height - 1;
    if (ml < 0)                 ml = 0;
    if (mr > window->Width - 1) mr = window->Width - 1;

    switch (Unit) {
        case UNIT_FRAME: {
              /*********\
              *         *
              *         *
              \*********/
            NewFrame[0].x = l;  NewFrame[0].y = t;
            NewFrame[1].x = r;  NewFrame[1].y = t;
            NewFrame[2].x = r;  NewFrame[2].y = b;
            NewFrame[3].x = l;  NewFrame[3].y = b;
            NewFrame[4].x = l;  NewFrame[4].y = t;
            ft = ShortFrame;
            break;
        }
        case UNIT_CHAR:
        case UNIT_WORD: {
            if (yt == yb) {   /* On the same line - same as UNIT_FRAME */
                NewFrame[0].x = l;  NewFrame[0].y = t;
                NewFrame[1].x = r;  NewFrame[1].y = t;
                NewFrame[2].x = r;  NewFrame[2].y = b;
                NewFrame[3].x = l;  NewFrame[3].y = b;
                NewFrame[4].x = l;  NewFrame[4].y = t;
                ft = ShortFrame;
            } else {
                      /*****\
                 ******     *
                 *          *
                 *      *****
                 *******/
                NewFrame[0].x = l;   NewFrame[0].y = t;
                NewFrame[1].x = mr;  NewFrame[1].y = t;
                NewFrame[2].x = mr;  NewFrame[2].y = yb;
                NewFrame[3].x = r;   NewFrame[3].y = yb;
                NewFrame[4].x = r;   NewFrame[4].y = b;
                NewFrame[5].x = ml;  NewFrame[5].y = b;
                NewFrame[6].x = ml;  NewFrame[6].y = yt + fh;
                NewFrame[7].x = l;   NewFrame[7].y = yt + fh;
                NewFrame[8].x = l;   NewFrame[8].y = t;
                ft = LongFrame;
            }
            break;
        }
        case UNIT_LINE: {
            NewFrame[0].x = ml;  NewFrame[0].y = t;
            NewFrame[1].x = mr;  NewFrame[1].y = t;
            NewFrame[2].x = mr;  NewFrame[2].y = b;
            NewFrame[3].x = ml;  NewFrame[3].y = b;
            NewFrame[4].x = ml;  NewFrame[4].y = t;
            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;
    }
    if (xl > maxx) {
        xl = maxx;
    }   
    if (xr < minx) {
        xr = minx;
    }   
}

/* 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, CHEIGHT * 2, 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))) {
                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))) {
                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))) {
                        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))) {
                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;
    LONG gzzx, gzzy, normalx, normaly;

      /* 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;
    }

      /* Try to get these as early as possible */
    gzzx = window->GZZMouseX;
    gzzy = window->GZZMouseY;
    normalx = window->MouseX;
    normaly = window->MouseY;

      /* 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;

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

      /* Find a position */
    xl = (GZZ ? gzzx : normalx) + window->RPort->Layer->Scroll_X;
    yt = (GZZ ? gzzy : normaly) + 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);
        struct TextFont *font[3];
        WORD fonts = 0;

          /* Collect fonts to try */
        if (SnapRsrc->AlternateFont) {      /* The alternate font */
            font[fonts++] = SnapRsrc->AlternateFont;
        }
        font[fonts++] = rp.Font;            /* Second best, the RP's font */
        if (cw) {                           /* Best bet, the cached font */
            font[fonts++] = cw->font;
        }
        while (fonts--) {
            SHORT xadj;
            SHORT yadj;
            SHORT seekw;
            SHORT seekh;
              /* Find out what we're trying to read */
            if (!SetSnapFont(font[fonts])) {
                continue;               /* Nope, try next font */
            }

            BltClear((char *)TempRaster, CHEIGHT * 2, 0L);
            if (cw) {
                xoff = - ((xl - cw->xoff) % cw->fw);
                yoff = - ((yt - cw->yoff) % cw->fh);
                ClipBlit(&rp, xl + xoff, yt + yoff,
                  &TempRp, 0L, 0L, (LONG)FontWidth, (LONG)FontHeight, COPY);
                WaitBlit();
                if (interpret(TempRaster)) {
                    goto found;
                }
            }
              /* No cache or cache didn't match */
            xadj = (FontWidth < 8 ? FontWidth : 8);
            yadj = (FontHeight < 12 ? FontHeight : 12);
            seekw = (xadj << 1) - FontWidth;
            seekh = (yadj << 1) - FontHeight;
            xl -= xadj;
            yt -= yadj;
            xoff = 0;
            while (xoff < seekw) {
                ClipBlit(&rp, xl + xoff, yt,
                  &TempRp, 0L, 0L, (LONG)FontWidth, CHEIGHT, COPY);
                WaitBlit();
                yoff = 0;
                while (yoff < seekh) {
                    if (interpret(&TempRaster[yoff])) {
                        goto found;
                    }
                    ++yoff;
                }
                ++xoff;
            }

              /* No character found. Try next font. */
            xl += xadj; /* Adjust to original values */
            yt += yadj;
        }
        UnlockLayer(LockedLayer);
        action = noaction;
        return 0;

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

        fw = FontWidth;
        fh = FontHeight;

        {
            SHORT temp = fh;
            while (temp <= fh + SnapRsrc->Leading) {  /* Check for extra pixel row */
                BltClear((char *)TempRaster, CHEIGHT * 2, 0L);
                ClipBlit(&rp, xl, yt + temp,
                  &TempRp, 0L, 0L, (LONG)FontWidth, (LONG)FontHeight, COPY);
                WaitBlit();
                if (interpret(TempRaster)) {
                    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;
            cw->font = font[fonts];
        } else {
            CacheWindow(window, xoff, yoff, fw, fh, font[fonts]);
        }
    }

      /* 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 = yoff +
      (((GZZ ? window->GZZHeight : window->Height) - yoff - fh) / 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;

        MyTR.tr_time.tv_secs = 0;
        MyTR.tr_time.tv_micro = 500000;
        MyTR.tr_node.io_Command = TR_ADDREQUEST;
        SendIO((struct IORequest *)&MyTR);

        sig = Wait(movesignal | cancelsignal | donesignal |
          clicksignal | ticksignal | timersignal);

        if ((sig & timersignal) && CheckIO((struct IORequest *)&MyTR)) {
            WaitIO((struct IORequest *)&MyTR);
            erase_frame();
            UnlockLayer(LockedLayer);   /* Unlock things */
            sig = Wait(ticksignal);     /* Wait for input handler to become avtive */
            LockLayer(0L, LockedLayer); /* Re-lock */
              /* I guess I should check to see that the window is still
                 there. Aw, what the heck. Apologies to anyone who gets
                 bitten by this. */
            DisplayBeep(NULL);
            update_frame();
        } else {
            AbortIO((struct IORequest *)&MyTR);
            WaitIO((struct IORequest *)&MyTR);
            SetSignal(0, timersignal);
        }

        if ((sig & ticksignal) && (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();
        }
    }
}
