
#include "explib.h"
#include "display.h"
#include "list.h"

#include "texter.h"

static void stripCRLF(char *p)
{
   char *p2;
   if (p2 = strchr(p, '\r'))   *p2 = 0;
   if (p2 = strchr(p, '\n'))   *p2 = 0;
   while(p2 = strchr(p, '\t')) *p2 = ' ';
}

static char *nextNonBlank(char *p)
{  while(*p && *p == ' ') p++; return p;  }

struct Symbol : public ListNode
{
   Symbol   (char *xid);
  ~Symbol   ();

   short    addContent(Symbol *s);
   short    addContents(char *cp);

   char *id;
   struct Symbol *next;
   struct Symbol *contents, *lastcontent;
   struct Symbol *instance[10];

   static ListHead instlist;
   static void    deleteall(void);  // incredibly rude, but it works
};

ListHead Symbol::instlist;

Symbol :: Symbol(char *xid)
 : id(strdup(xid)),
   next(0),
   contents(0),
   lastcontent(0)
{_
//  dmsg("Symbol ctr %lxh",this);
    memset(instance, 0, sizeof(instance));  // all unset
    char *cp;
    while(cp = strchr(id, ';'))  *cp = ',';
    instlist.add(this);
}

Symbol :: ~Symbol()
{_
//   dmsg("Symbol dtr %lxh",this);
   instlist.remove(this);

/*
   Symbol *s,*n;
   for(s=contents; s; s=n)
   {
      n=s->next;
      delete s;
   }
*/

   if(id) free(id); // due to strdup
}

void Symbol::deleteall(void)
{
    Symbol *s;
    while(s = (Symbol*)instlist.first())
    {
//      dmsg("remdel sym %lxh",s);
        delete s;
    }
}

short Symbol::addContent(Symbol *s)
{_
// dmsg("   Symb \"%s\" new content \"%s\"", id, s->id);
   if(lastcontent) { lastcontent->next = s; lastcontent = s; }
   else  contents = lastcontent = s;
   return 0;
}

short Symbol::addContents(char *cp)
{_
   char *tok;
   for(tok = strtok(cp, ",\n"); tok; tok = strtok(0, ",\n"))
      addContent(new Symbol(tok));
   return 0;
}

//  ============ Texter ===========

Symbol *Texter :: getsymb(char *symb)
{_
   for(Symbol *s=first; s && strcmp(s->id, symb); s=s->next);
   return s;   // if any
}

rcode Texter :: solve(char *symb)
{_
   Symbol *s = getsymb(symb);
   if(!s)   {perr("can't solve %s", symb); return FAILED;}
   sbuf[0]  = 0;  // reset solver statement buffer
   return solve(s, 0, -1);
}

rcode Texter :: solve(Symbol *s, short nest, short instsel)
{_
   static char *blanks = "                                           ";
   dmsg("%*.s%s",(sysint)nest,blanks,s->id);

   if(nest > 20)  {perr("recursion overflow"); return FAILED;}

   Symbol *c;

   if(instsel != -1 && s->instance[instsel])
      c = s->instance[instsel];
   else
   {
       long ncontents = 0;
       // select random content (if more than 1)
       for(c=s->contents; c; c=c->next)
          ncontents++;
       long icont = rand() % ncontents;
       long i = 0;
       for(c=s->contents; c; c=c->next)
          if(i++ >= icont)
             break;

       if(instsel)
       {
          for(short bailout=0; bailout<10; bailout++)
          {
              // is 'c' already selected in another instance slot?
              for(short i=0; i<10; i++)
                 if(s->instance[i] == c)
                    break;

              if(i==10)
                break;  // no: found valid c

              // yes: random-select (hopefully another) c
              icont = rand() % ncontents;
              i = 0;
              for(c=s->contents; c; c=c->next)
                 if(i++ >= icont)
                     break;
          }

          s->instance[instsel] = c;
       }
   }

   // solve content -> make pure text from it
   Symbol *p;
   char *cp,*cp2,*cp3,tok[100];

   for(cp=c->id; *cp; cp=cp2)
   {
      cp=nextNonBlank(cp);
      cp2=cp;
      if(*cp2 == '$') cp2++;
      while(*cp2 && !strchr(" $", *cp2))
         cp2++;

      if(cp2-cp >= sizeof(tok))
      { perr("token buffer overflow"); return MEMORY; }

      strncpy(tok, cp, cp2-cp);
      tok[cp2-cp] = 0;

//    dmsg("cp %s cp2 %s",cp,cp2);

      if(tok[0] == '$')
      {
         cp3=&tok[1];

         if(isdigit(*cp3))
         {
            instsel = *cp3 - '0';
            cp3=&tok[2];
         }
         else
            instsel = -1;

         if(!(p=getsymb(cp3)))
            perr("unknown token: \"%s\"", cp3);
         else
            if(solve(p, ++nest, instsel))
                return FAILED;
      }
      else
      {
         if(strlen(sbuf) + strlen(tok) < sizeof(sbuf))
         {
             strcat(sbuf, tok);
             strcat(sbuf, " ");
             if(cp = strstr(sbuf, " ."))    // i know it's horrid...
                memmove(cp,cp+1,strlen(cp));
         }
         else
         {   perr("buffer overflow"); return MEMORY;    }
      }
   }

   return OK;
}

Texter :: Texter(char *fname)
 : root(0),
   first(0),
   last(0)
{_
   bClIsDead = 1;

   FILE *f = fopen(fname, "r");
   if (!f) return;

   char buf[200], *cp;
   Symbol *cur = 0;

   while(fgets(buf, sizeof(buf), f))
   {
      stripCRLF(buf);

      if (!strlen(buf))
         continue;   // empty line

      if (buf[0] == ' ')
      {
         cp = nextNonBlank(buf);

         if (!strlen(cp))
            continue;   // blank line

         // further raw definitions of current symbol:
         if (!cur)
         {
            perr("no current symbol - %s in vain", buf);
            return;
         }

         cur->addContents(cp);
      }
      else
      {
         // a new symbol:
         cp = strchr(buf, ':');
         if (!cp)
         {
            perr("illegal syntax: %s", buf);
            return;
         }
         *cp = 0;
         cur = new Symbol(buf);

         if(last) { last->next = cur; last = cur; }
         else       first = last = cur;

         cur->addContents(cp+1);
      }
   }

   fclose(f);

   bClIsDead = 0;
}

Texter::~Texter()
{_
   Symbol::deleteall();
/*
   Symbol *s,*n;
   for(s=first; s; s=n)
   {
      n=s->next;
      delete s;
   }
*/
}

/*
int main(int argc, char *argv[])
{
   time_t tm;
   srand((unsigned short)time(&tm));

   Texter tst("story.dat");

   dmsg("=== list of symbols: ===");
   for(Symbol *s=tst.first; s; s=s->next)
      dmsg("%s", s->id);

   dmsg("");
   tst.solve("all");

   return 0;
}
*/

rcode showFormattedText(
    char *text,
    PixelDisplay &pd,
    cpix x,cpix y, cpix w, cpix h
    )
{
    char  buf[80], *cp, *cp2;

    // pass 1: "damned, how many lines are there?"

    short nlines = 0;
    for(cp = text; strlen(cp) > 40;)
    {
      if((cp2 = strchr(cp, '@')) && (cp2-cp) < 40)
      {
          cp = cp2+1;
      }
      else
      {
          for(cp2=cp+40; cp2>cp+10 && *cp2!=' '; cp2--);
          cp = cp2;
      }
      nlines++;
    }
    if(strlen(cp))
      nlines++;

    if(!nlines)
      return INDATA;  // nothing to show

    // pass 2: show it

    cpix  frame[4] = {x,y,x+w,y+h};

    short nchars  = strlen(text);
    short width   = frame[2] - frame[0];
    short height  = frame[3] - frame[1];
    short cwidth  = width  / 50;
    short cheight = height / nlines;
    RECT rhl;

    HFONT hfont = CreateFont(
                     cheight,cwidth,    // height, width
                     0,0,       // angle, angle
                     700,       // weight
                     0,0,0,     // italic,underline,strikeout
                     ANSI_CHARSET,
                     OUT_DEFAULT_PRECIS,
                     CLIP_DEFAULT_PRECIS,
                     DEFAULT_QUALITY,
                     DEFAULT_PITCH|FF_DONTCARE,
                     "times new roman"  // fontname
                     );
    if(hfont)
    {
       HANDLE hold = SelectObject(pd.getcurhdc(), hfont);

       if(hold)
       {
          SetBkMode(pd.getcurhdc(), TRANSPARENT);

          short y = frame[1];
          short dshadow = 2;

          for(cp = text; strlen(cp) > 40;)
          {
            if((cp2 = strchr(cp, '@')) && (cp2-cp) < 40)
            {
                sprintf(buf, "%.*s", (sysint)(cp2-cp), cp);
                cp = cp2+1;
            }
            else
            {
                for(cp2=cp+40; cp2>cp+10 && *cp2!=' '; cp2--);
                sprintf(buf, "%.*s", (sysint)(cp2-cp), cp);
                cp = cp2;
            }

            rhl.left      = frame[0]  + dshadow;
            rhl.right     = frame[2];
            rhl.top       = y         + dshadow;
            rhl.bottom    = y+cheight;
            SetTextColor(pd.getcurhdc(), RGB(50,50,50));
            DrawText( pd.getcurhdc(), buf, -1, &rhl, DT_CENTER|DT_VCENTER);

            rhl.left      = frame[0];
            rhl.right     = frame[2]  - dshadow;
            rhl.top       = y;
            rhl.bottom    = y+cheight - dshadow;
            SetTextColor(pd.getcurhdc(), RGB(255,0,0));
            DrawText( pd.getcurhdc(), buf, -1, &rhl, DT_CENTER|DT_VCENTER);

            y  += cheight;
          }

          if(strlen(cp))
          {
            sprintf(buf, "%s", cp);

            rhl.left      = frame[0]  + dshadow;
            rhl.right     = frame[2];
            rhl.top       = y         + dshadow;
            rhl.bottom    = y+cheight;
            SetTextColor(pd.getcurhdc(), RGB(50,50,50));
            DrawText( pd.getcurhdc(), buf, -1, &rhl, DT_CENTER|DT_VCENTER);

            rhl.left      = frame[0];
            rhl.right     = frame[2]  - dshadow;
            rhl.top       = y;
            rhl.bottom    = y+cheight - dshadow;
            SetTextColor(pd.getcurhdc(), RGB(255,0,0));
            DrawText( pd.getcurhdc(), buf, -1, &rhl, DT_CENTER|DT_VCENTER);
          }

          SelectObject(pd.getcurhdc(), hold);
       }

       DeleteObject(hfont);
    }

    return OK;
}

rcode showFormattedStory(
    char *datfile,
    PixelDisplay &pd,
    cpix x, cpix y, cpix w, cpix h
    )
{
    Texter texter(datfile);

    if(texter.isdead())
        return FAILED;

    if(texter.solve("all"))
        return FAILED;

    showFormattedText(texter.result(), pd, x,y,w,h);

    return OK;
}
