
/* SP.C             Robert A. Hearn */

#include <types.h>
#include <memory.h>
#include "sp.h"
#include "spdef.h"
#include "sc.h"
#include "scparam.h"
#include "string.h"
/*#include "ctype.h"*/
#include "stdio.h"
#include "query.h"
#include "clam.h"
#include "lex.h"
#include "corelex.h"
#include "driver.h"

/* SP.C: This file contains the SP routines, which interface to the Proximity
   routines.  The assembly language interface to these routines is in spint.src.
   Unless I hand-code some of these, which seems fairly probable. */

extern char *SPJumpLoc[];
extern unsigned _SPActive[];
extern unsigned _THActive;

/* SP private globals */

unsigned _SPListAlts;
unsigned _SPXVal;
unsigned _SPID;
long     _SPHandler;
char	*_SPdir;
char	*WcurDoc;				/* so it can be updated */
char	*SPCancelled = "\pSpell checking was cancelled.";
char	*SPBroken = "\pCould not open the user dictionary.\rIt may be corrupt.";

/* Stuff local to sp.c */

static unsigned  inhyph;        /* In hypenated word to scan word-by-word   */
static unsigned  mustbecap;     /* This word must be capitalized            */
static unsigned  repeating;     /* Calling GetWord at same place as before  */
static unsigned  wantperiod;    /* GetWord should attach ending pd to word  */
static unsigned  firsttime;     /* First time through SPCheck?              */
static char      *cmndtab;      /* Command char table                       */
static char lastword[MAXWORD];	/* Previous word                            */
static unsigned  lastflags;     /* Previous word's flags                    */
static unsigned  cancelled;     /* Cancelled?                               */
static unsigned  errcode;       /* What spelling error occurred?            */
static unsigned  result;

/* These are maintained by lots of things. */

static char *sp;                /* Word start ptr                           */
static char *ep;                /* Word end ptr                             */
static char *rp;                /* Ptr to rest of text (after endpunc)      */
char *prevp;					/* pointer to the end of the previous word	*/

/* These are maintained by GetWord. */

static char wbuf[MAXWORD];    	/* Word buffer                              */
static unsigned  flags;         /* Word characteristics                     */
static char epfirst;            /* First chararacter in end punctuation     */
static char eplast;             /* Last character in end punctuation        */
static char epnum;              /* Number of end punctuation chars          */

/* This is maintained by CheckWord. */

static char corbuf[MAXWORD];    /* Corrected word if SPCMISPUNC or SPCHYPH. */

/* This is for the udict stuff */

CLAM *_SPudtable[SPMAXUDICT];   /* CLAM ptrs */
char **_SPudpn[SPMAXUDICT];     /* Pathname handles */

/* Static declarations for wimpy C compiler */

static int          NextChar();
static unsigned     EatWhite();
static unsigned     GetWord();
static unsigned     CheckWord();
static void         MakeLower();
static unsigned     GetUDSlot();

extern pascal unsigned _SPMetaHandler();


/* Startup the spell checker. */

pascal void _SPStartUp(mmid, spdir)
unsigned mmid;
char *spdir;
{
unsigned  len;
unsigned  x;

    if (_SPActive[0])
        SPErr2(SPACTIVE, 0);
    _SPErrNum[0] = 0;

    _SPID = _SPTHID[0] = mmid + SPTHID;
    toolErr[0] = &_toolErr;                 /* &%^$%$!@@!@!! */

    if (!meminit())
        SPErr2(SPNOMEM, 0);

	len = *(unsigned *) spdir;
	_SPdir = spdir+2;
	_SPdir[len] = '\0';

    if (!clxopen(1, SP_CORRECT + SP_DETECT))
        if (*toolErr[0])
        {
            SPErr2(SPPRODOS, *toolErr[0]);
        }
        else
        {
            SPErr2(SPCANTDOIT, 0);
        }

    strcat(_SPdir, "Dictionary");

    if (!lexopen(2, SP_CORRECT + SP_DETECT, _SPdir))
    {
        if (*toolErr[0])
        {
            SPErr2(SPPRODOS, *toolErr[0]);
        }
        else
        {
            SPErr2(SPCANTDOIT, 0);
        }
    }

    for (x = 0; x < SPMAXUDICT; ++x)
        _SPudtable[x] = NULL;

    _SPActive[0] = 1;
}

/* Shut down SP */

pascal void _SPShutDown()
{
unsigned x;

    if (!_SPActive[0])
        SPErr2(SPINACTIVE, 0);

    /* Close the dictionaries */

    for (x = 0; x < SPMAXUDICT; ++x)
        if (_SPudtable[x])
        {
            clamclose(_SPudtable[x]);
            DisposeHandle(_SPudpn[x]);
        }
    clxclose();

    lexclose();

    _SPErrNum[0] = 0;
    _SPActive[0] = 0;
}

/* Spell-check the text at tp. */

pascal void _SPCheck(tp, ct, mode, h, curdoc, wcancel)
char *tp;                   /* Ptr to to text                           */
char *ct;                   /* Ptr to command table                     */
unsigned mode;              /* Initial mode                             */
long h;                     /* Error handler - must lie to the compiler
                               about type.  'pascal' keyword not valid here. */
char *curdoc;				/* in case it has to be updated */
unsigned *wcancel;			/* if you cancel, let WP know about it */
{

   if (!_SPActive[0])
       SPErr2(SPINACTIVE, 0);
   _SPErrNum[0] = 0;

	WcurDoc = curdoc;
	
    if (!*Clxdata)
    {
        clxfree();
        if (!getacorelex())
            SPErr2(SPNOMEM, 0);
    }

    if (mode == SPMSENT)
        mustbecap = 1;
    else if (mode == SPMNOSENT)
        mustbecap = 0;
    else
        SPErr2(SPBADMODE, 0);

    inhyph = repeating = wantperiod = 0;

    lastword[0] = 0;
    flags = 0;
    sp = tp;
    _SPHandler = h;         /* make it global */
    cmndtab = ct ? ct : _SPdeftab;

    while (1)
    {		
		asm {
			jsl D_BEACHBALL
			bcc OK
		}
		*wcancel = 1;
		(void) D_ALERTBOX(okBox,SPCancelled);
		return;
OK:
        if (!EatWhite())
            SPErr2(_SPErrNum[0], _SPXVal);   /* _SPErrNum may be 0 */

       /* GetWord gets a word and determines its flags. */

		prevp = ep;
       if (!GetWord())
       {
           if (_SPErrNum[0])
               SPErr2(_SPErrNum[0], _SPXVal);

           /* Hit a final break... quit if cancel or buf empty. */

           if (cancelled || !wbuf[0])
               return;
       }


        /* See if adding a period is required and we have one to attach
           (determined in CheckWord).  If so, there may still be other
           punctuation errors. */

        if ((errcode = CheckWord()) == WANTPD)
        {
            strcat(wbuf, ".");
            if (isword(wbuf))
            {
                /* Rescan the word, taking final period this time.  May seem
                   a little extreme, but the state information is needed, and
                   other errors may occur. */

                wantperiod = repeating = 1;
                continue;
            }
            else
            {
                wbuf[strlen(wbuf) - 1] = 0;
                errcode = SPCMISSPELL;
            }
       }

       if (errcode)
       {
           /* If it has hyphens and its unhyphenated version is not a
              word, try looking at it word by word. */

           if (flags & THYPHEN && errcode != SPCHYPH)
           {
               repeating = inhyph = 1;
               continue;
           }
           else
           {
               switch((result = _SPMetaHandler(errcode, sp, ep, corbuf, prevp)) &
                 CODEMASK)
               {
				   case SPHACCEPT:
                       break;
                   case SPHREPLACE:
                       repeating = 1;
 				   case SPHJUMP:
                       if (SPJumpLoc[0])
                       {
                           sp = SPJumpLoc[0];
                           SPJumpLoc[0] = NULL;
                       }
                       continue;
                   case SPHCANCEL:
                   case SPHFINISH:
                       return;
                   default:
                       SPErr2(SPBADRESULT, result);
               }
           }
       }

       strcpy(lastword, wbuf);
       MakeLower(lastword);
       lastflags = flags;

       inhyph = inhyph && flags & BEFOREHYPH;
       mustbecap = !(flags & BEFOREHYPH + BEFORESLASH) && (eplast == '.' ||
         eplast == '?' || eplast == '!') && (epnum == 1);
       repeating = 0;

       sp = rp;
   }
}

/* Suggest correct spellings for wptr (presumed incorrect).  Return corrections
   in Pascal format. */

static char *justcor[2];

pascal char **_SPSuggest(wptr, listalts)
char *wptr;
unsigned listalts;
{
char **result;
char **rp;

   if (!_SPActive[0])
       SPErr(SPINACTIVE, 0, NULL);
   _SPErrNum[0] = 0;

    if (!*Clxdata)
    {
        clxfree();
        if (!getacorelex())
            SPErr(SPNOMEM, 0, NULL);
    }

   if ((_SPListAlts = listalts) > MAXLISTALTS)
       SPErr(SPTOOMANY, 0, NULL);

   if (wptr == NULL)
   {
       /* Use the word SPCheck gave to the handler.  If SPCMISPUNC or
          SPCHYPH, just give corbuf (already in Pascal format). */

       if ((errcode == SPCMISPUNC) || (errcode == SPCHYPH) || (errcode ==
         SPCCAP))
       {
           justcor[0] = corbuf;
           justcor[1] = NULL;
           return(justcor);
       }

       result = correct(wbuf);
   }
   else
   {
       p2cstr(wptr);
       result = correct(wptr);
       c2pstr(wptr);
   }

   for (rp = result; *rp; ++rp)
       c2pstr(*rp);

   return(result);
}

/* Open a previously created user dictionary */

pascal unsigned _SPOpenUDict(name)
char *name;	/* this is already a C string */
{
unsigned refnum;

    if (!_SPActive[0])
        SPErr(SPINACTIVE, 0, 0);
    _SPErrNum[0] = 0;

    _SPTHID[0] = _SPID;

    /* Find an unused udict slot if there is one */

    for (refnum = 0; refnum < SPMAXUDICT; ++refnum)
        if (!_SPudtable[refnum])
            break;

    if (_SPudtable[refnum])
        SPErr(SPTOOMANY, 0, 0);


    if (!(_SPudtable[refnum] = clamopen(refnum + 3, SP_CORRECT + SP_DETECT,
      name, 0, 0L)))
    {
        if (*toolErr[0])
        {
            SPErr(SPPRODOS, *toolErr[0], 0);
        }
        else
        {
            SPErr(SPCANTDOIT, 0, 0);
        }
    }

    _SPudpn[refnum] = (char **) NewHandle((long) strlen(name) + 1, _SPID,
      0x0010, 0L);
    strcpy(*_SPudpn[refnum], name);


    return(refnum + 1);         /* Publicly, refnums begin at 1 */
}



/* ¥¥¥ Dead Code

/* Close a user dictionary *

pascal void _SPCloseUDict(refnum)
unsigned refnum;
{
    if (!_SPActive[0])
        SPErr2(SPINACTIVE, 0);
    _SPErrNum[0] = 0;

    --refnum;
    if (refnum < 0 || refnum >= SPMAXUDICT)
        SPErr2(SPBADREF, 0);
    if (!_SPudtable[refnum])
        SPErr2(SPBADREF, 0);

    clamclose(_SPudtable[refnum]);
    _SPudtable[refnum] = NULL;
    DisposeHandle(_SPudpn[refnum]);
}

*/


/* Create a new user dictionary */

pascal void _SPNewUDict(name, ftype, atype)
char *name;	/* already a C string */
unsigned ftype;
long atype;
{
CLAM *cp;

    if (!_SPActive[0])
        SPErr2(SPINACTIVE, 0);
    _SPErrNum[0] = 0;

    _SPTHID[0] = _SPID;


    /* Possible list #s are 1 - 10; use 11 */

    if (!(cp = clamopen(11, CL_INIT, name, ftype, atype)))
    {
        SPErr2(SPPRODOS, *toolErr[0]);
    }

    clamclose(cp);

}

/* Add a word to a user dictionary */

pascal void _SPAddWord(refnum, word)
unsigned refnum;
char *word;
{
	char buf[2*MAXWORD];
	unsigned flags;
	char *thePath, *pos;
	HANDLE f;

	extern char *c2pstr1();
	extern HANDLE stdcreate();
	extern void stdclose();



    if (!_SPActive[0])
        SPErr2(SPINACTIVE, 0);
    _SPErrNum[0] = 0;

    _SPTHID[0] = _SPID;

    if (refnum < 1 || refnum > SPMAXUDICT)
        SPErr2(SPBADREF, 0);
    if (!_SPudtable[refnum - 1])
        SPErr2(SPBADREF, 0);

	/* see if we have some disk space */

	thePath = *_SPudpn[refnum-1];
	if (pos = strrchr(thePath,':'))
		pos++;
	else
		pos = thePath;
	strcpy(pos, "bobo.temp");
	if ((f = stdcreate(thePath, 0, 0L)) != H_ERROR) {
		char *path1;
		
		stdclose(f);
		f = 0;
		if ((path1 = c2pstr1(thePath)) != (char *) 0xFFFFFFFFL) {
			D_DESTROY2(path1);
			D_DISPOSEPTR(path1);
		}
	} else
		D_ALERTBOX(okBox,"\pThe word could not be added.");
	strcpy(pos,"User.Dictionary");
	if (f)
		return;
		

    p2cstr(word);
    flags = doflags(word, buf, MAXWORD);

    if (clamadd(buf, flags, _SPudtable[refnum - 1]) != OKAY)
    {
        c2pstr(word);
        if (*toolErr[0])
        {
           _SPErrNum[0] = SPPRODOS; 
		   _SPXVal = *toolErr[0];
        }
        else
        {
           _SPErrNum[0] = SPCANTDOIT; 
		   _SPXVal = 0;
        }
    }
    c2pstr(word);
	/* force a flush of any buffers */
	clamclose(_SPudtable[refnum-1]);
	if (!(_SPudtable[refnum-1] = clamopen(refnum+2, SP_DETECT+SP_CORRECT, *_SPudpn[refnum-1], 0xD0, 0x8001L)))
		D_ALERTBOX(okBox,SPCancelled);

}


/*                          */
/* Support routines follow  */
/*                          */


/* NextChar finds the next real character from a specified position.  Positions
   the pointer there and returns the char, or -1 if nonrecoverable break.
   Char may be different from what is pointed to (if SPHSUBST).  Sets cancelled
   if cancelled. */

static int NextChar(tpp)
char **tpp;
{
unsigned isreal;

    while (1)
    {
        if ((isreal = cmndtab[(unsigned) **tpp]) > 1)
        {
            *tpp += isreal - 1;
            continue;
        }
        if (isreal)
           return((unsigned) **tpp);
        switch ((result = _SPMetaHandler(SPCBREAK, *tpp, 0L, NULL, 0L)) & CODEMASK)
        {
            case SPHJUMP:
                *tpp = SPJumpLoc[0];
                SPJumpLoc[0] = NULL;
                continue;
            case SPHSUBST:	/* this shouldn't be possible.  The handler in WP isn't capable */
                return (result & DATAMASK);
            case SPHCANCEL:
                cancelled = 1;
            case SPHFINISH:
                return(-1);
            default:
                SPErr(SPBADRESULT, 0, -1);
        }
    }
}

/* Eat (optional) white space characters.  Leave sp pointing to the char we
   stopped on.  If we run into a break we can't skip, return 0. */

static unsigned EatWhite()
{
int c;

   while (1)
   {
       if ((c = NextChar(&sp)) == -1)
           return (0);
       if (!(chartype[c] & TWSPACE))
           return (1);
       ++sp;
   }
}

/* Put the next word into wbuf.  Set flags. */

static unsigned GetWord()
{
int c;
unsigned buflen;
unsigned endpos;
unsigned stop;
unsigned sawchar;
unsigned endtypes = 0;
unsigned finished = 0;
unsigned ctype;
unsigned apost=0;

    if (repeating)
        flags &= AFTERSLASH + AFTERHYPH + HASBEGINPUNC;
    else if (flags & BEFORESLASH)
        flags = AFTERSLASH;
    else if (flags & BEFOREHYPH)
        flags = AFTERHYPH;
    else flags = 0;

    /* Empty the buffer now, in case we exit early */

    wbuf[0] = 0;

    /* Skip beginning punctuation.  OK if we end here, because isolated
       punctuation is a special case of a nonalphanumeric word, which we
       accept. */

    for (c = NextChar(&sp); c != -1 && chartype[c] & TBPUNC; ++sp, c =
      NextChar(&sp))
        flags |= HASBEGINPUNC;
    if (c == -1)
        return (0);

	ctype = chartype[c];
	
    stop = TWSPACE + TSLASH + (inhyph ? THYPHEN : 0);
    rp = sp;
    sawchar = 0;
    ep = NULL;
    buflen = 0;

    /* Find the end of the word (ep) and of the token (rp), building the
       buffer and the flags. */

    while (!(ctype & stop))
    {
        if (!sawchar && ctype & (TLETTER + TDIGIT))
        {
            ep = NULL;
            epfirst = eplast = epnum = 0;
            sawchar = 1;
            flags |= endtypes;
        }
        else if (sawchar && (ctype & TEPUNC) && !(wantperiod && c == '.'))
        {
            ep = rp;
			if (buflen >= MAXWORD)
				endpos = MAXWORD-1;
			else
				endpos = buflen;
				
            epfirst = eplast = c;
            epnum = 1;
            sawchar = endtypes = 0;
        }
        else if (ctype & TEPUNC)
        {
            eplast = c;
            ++epnum;
            endtypes |= ctype;
        }

        if (buflen < MAXWORD)
            wbuf[buflen] = (char) c;
	    ++buflen;

		if (c == '\'' || c == 'Õ') {
			apost = 1;					/* we've seen "Õ" */
		} else if (c == 's' && apost == 1) {
			apost = 2;					/* we've seen "Õs" */
		} else if (apost == 2) {
			if (!(ctype & TEPUNC)) {
				apost = 0;					/* we've seen "Õsx", so blow it off */
				flags |= chartype['\''] | chartype['s'];
			} else
				apost = 2;
		} else							/* nothin... */
			apost = 0;
			
        if (sawchar)
        {
			if (!apost)					/* This way, the flags for "Õ" and "s" don't get */
	  			flags |= ctype;			/* erroneously set.  E.g., "123Õs" */
				
            if (buflen >= MAXWORD)
                flags |= OVERFLOW;
        }

        ++rp;
        if ((c = NextChar(&rp)) == -1)
            if (cancelled)
                return(0);
            else
            {
                ++finished;
                break;
            }
		ctype = chartype[c];
    }

    if (!ep)
    {
        ep = rp;
		if (buflen >= MAXWORD)
			endpos = MAXWORD-1;
		else
			endpos = buflen;
    }
    else
        flags |= HASENDPUNC;

    if (c == '/' || c == 'Ú' || c == 'Ö')
    {
        ++rp;
        flags |= BEFORESLASH;
    }
    else if (c == '-' || c == 'Ð' || c == 'Ñ')
    {
        flags |= BEFOREHYPH;
        ++rp;
    }
    flags &= ~(TBPUNC + TEPUNC);

    wbuf[endpos] = '\0';

    /* Strip 's and set HASPOSS */

    if (endpos > 2 && ((c = wbuf[endpos - 2]) == '\'' || c == 'Õ') && wbuf[endpos - 1] == 's')
    {
        flags |= HASPOSS;
        ep -= 2;
        wbuf[endpos - 2] = '\0';
    }

    wantperiod = 0;

    return (!finished);
}

static unsigned CheckWord()
{
char *p, *q, c;

   /* Pass it if it's non-alphabetic */

   if (!(flags & TLETTER))
       return(0);

   if (flags & OVERFLOW)
   {
       wbuf[MAXWORD-1] = 0;
       return (SPCMISSPELL);
   }

   strcpy(corbuf, wbuf);

   if (!(flags & (BEFORESLASH + AFTERSLASH + BEFOREHYPH + AFTERHYPH +
     HASBEGINPUNC + OVERFLOW)) && !(lastflags & (BEFORESLASH + AFTERSLASH
     + BEFOREHYPH + AFTERHYPH + HASENDPUNC + OVERFLOW)))
   {
       MakeLower(corbuf);
       if (!strcmp(lastword, corbuf))
           return(SPCREPEAT);
   }

   if (strlen(wbuf) == 1 && (epfirst == '.' || epfirst == ')'))
       return(0);

   if (!isword(wbuf))
   {
        /* See if it has spurious hyphens */

        if (flags & THYPHEN)
        {
            for (p = wbuf, q = corbuf; *p; ++p)
                if ((c = *p) != '-' && c != 'Ð' && c != 'Ñ')
                    *q++ = *p;
            *q = 0;
            if (isword(corbuf))
            {
                c2pstr(corbuf);
                return(SPCHYPH);
            }
        }

        /* See if we just need to add a period */

        if (!(Quflags & (IW_ENDDOT + IW_ALLDOT)) && epfirst == '.')
            return (WANTPD);
        else if (Quwlist > 0)
        {
            undoflags(Qulookup, Quwflags, corbuf);
            if ((Quwflags & IW_CASE) < (Quflags & IW_CASE))
                undoflags(corbuf, Quflags & IW_CASE, corbuf);
            c2pstr(corbuf);
            return(SPCMISPUNC);
        }

        return(SPCMISSPELL);
    }

   if (mustbecap && islower(wbuf[0]))
   {
       corbuf[0] = _toupper(corbuf[0]);
       c2pstr(corbuf);
       return(SPCCAP);
   }

   return(SPNOERR);   
}

static void MakeLower(s)
char *s;
{
   --s;
   while (*++s)
       *s = tolower(*s);
}


SPCloseFiles()
{
unsigned x;

    setbank;

    /* Close main dict */

    stdclose(Lexfile);

    /* Close the udicts */

    for (x = 0; x < SPMAXUDICT; ++x)
        if (_SPudtable[x])
            stdclose(_SPudtable[x]->cl_file);

    restorebank;
}

