/* #includes */ /*{{{C}}}*//*{{{*/
#undef  _POSIX_SOURCE
#define _POSIX_SOURCE   1
#undef  _POSIX_C_SOURCE
#define _POSIX_C_SOURCE 2

#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <locale.h>
#include <nl_types.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <getopt.h>
#include <config.h>

#include "misc.h"
#include "sentence.h"
/*}}}*/
/* #defines */ /*{{{*/
#define USAGE1    catgets(catd,1, 1,"Usage: diction [-f file [-n|-L language]] [file ...]\n")
#define USAGE2    catgets(catd,1, 2,"       diction [--file [--no-default-file|--language]] [file ...]\n")
#define USAGE3    catgets(catd,1, 3,"       diction --version\n")
#define MORE      catgets(catd,1, 4,"Try diction -h|--help for more information.\n")
#define HELP1     catgets(catd,1, 5,"Print wordy and commonly misused phrases in sentences.\n")
#define HELP2     catgets(catd,1, 6,"-f, --file             also read the specified database\n")
#define HELP3     catgets(catd,1, 7,"-n, --no-default-file  do not read the default phrase file\n")
#define HELP4     catgets(catd,1, 8,"-L, --language         set document language\n")
#define HELP5     catgets(catd,1, 9,"-h, --help             print this message\n")
#define HELP6     catgets(catd,1,10,"    --version          print the version\n")
#define NOFILE    catgets(catd,1,11,"diction: can not open %s: %s\n")
#define NOMEM     catgets(catd,1,12,"diction: out of memory.\n")
#define DOUBLE    catgets(catd,1,13,"Double word.")
#define NOSENT    catgets(catd,1,14,"No sentences found.\n")
#define NOPHRASES catgets(catd,1,15,"No phrases ")
#define ONEPHRASE catgets(catd,1,16,"1 phrase ")
#define NPHRASES  catgets(catd,1,17,"%d phrases ")
#define ONESENT   catgets(catd,1,18,"in 1 sentence found.\n")
#define NSENT     catgets(catd,1,19,"in %d sentences found.\n")
/*}}}*/

/* types */ /*{{{*/
struct word
{
  char *word;
  size_t size;
};

struct badPhrase
{
  char *phrase;
  char *suggest;
  int next; /* index of next entry with different first letter */
};
/*}}}*/

static struct badPhrase *badPhrases=(struct badPhrase *)0;
static int badPhraseCapacity=0;
static int badPhraseSize=0;
static struct word *wordQueue;
static int wordQueueLength;
static int wordQueueStart;
static int sentences,hits;
static nl_catd catd;
static int phraseStart[256];

static void loadPhrases(const char *file) /*{{{*/
{
  FILE *fp;
  char ln[1024];
  const char *tab;
  size_t l;
  int fix,j;

  if ((fp=fopen(file,"r"))==(FILE*)0)
  {
    fprintf(stderr,NOFILE,file,strerror(errno));
    exit(1);
  }
  while (fgets(ln,sizeof(ln),fp))
  {
    char c=ln[0]==' ' ? ln[1] : ln[0];
    if (c && c!='\n')
    {
      phraseStart[(unsigned int)((unsigned char)c)]=1;
    }
    if (badPhraseSize==badPhraseCapacity) /* enlarge capacity */ /*{{{*/
    {
      if ((badPhrases=realloc(badPhrases,(badPhraseCapacity=3*(badPhraseCapacity+32))*sizeof(struct badPhrase)))==(struct badPhrase*)0)
      {
        fprintf(stderr,NOMEM);
        exit(2);
      }
    }
    /*}}}*/
    if ((tab=strchr(ln,'\t'))) /* load phrase and suggestion */ /*{{{*/
    {
      if ((badPhrases[badPhraseSize].phrase=malloc(tab-ln+1))==(char*)0)
      {
        fprintf(stderr,NOMEM);
        exit(2);
      }
      strncpy(badPhrases[badPhraseSize].phrase,ln,tab-ln);
      badPhrases[badPhraseSize].phrase[tab-ln]='\0';
      if ((badPhrases[badPhraseSize].suggest=malloc(l=strlen(tab+1)))==(char*)0)
      {
        fprintf(stderr,NOMEM);
        exit(2);
      }
      strncpy(badPhrases[badPhraseSize].suggest,tab+1,l);
      if (badPhrases[badPhraseSize].suggest[l-1]=='\n') badPhrases[badPhraseSize].suggest[l-1]='\0';
    }
    /*}}}*/
    else if (ln[0]!='\n') /* load superfluous phrase */ /*{{{*/
    {
      if ((badPhrases[badPhraseSize].phrase=malloc(l=strlen(ln)))==(char*)0)
      {
        fprintf(stderr,NOMEM);
        exit(2);
      }
      strncpy(badPhrases[badPhraseSize].phrase,ln,l);
      if (badPhrases[badPhraseSize].phrase[l-1]=='\n') badPhrases[badPhraseSize].phrase[l-1]='\0';
      badPhrases[badPhraseSize].suggest=(char*)0;
    }
    /*}}}*/
    ++badPhraseSize;
  }
  /* resolve =phrase explainations */ /*{{{*/
  for (fix=0; fix<badPhraseSize; ++fix)
  {
    if (badPhrases[fix].suggest && *badPhrases[fix].suggest=='=')
    {
      for (j=0; j<badPhraseSize; ++j)
      {
        if (j!=fix && strcmp(badPhrases[j].phrase,badPhrases[fix].suggest+1)==0)
        {
          free(badPhrases[fix].suggest);
          badPhrases[fix].suggest=badPhrases[j].suggest;
          break;
        }
        if (j==badPhraseSize)
        {
          fprintf(stderr,"diction: Warning: Unable to resolve %s.\n",badPhrases[fix].suggest);
        }
      }
    }
  }
  /*}}}*/
}
/*}}}*/

static void diction(const char *s, size_t length, const char *file, int line) /*{{{*/
{
  const char *lastout=s;
  size_t i;
  int j;
  int inword=0;
  int wordLength=-1;

  if (length==0) return;
  for (i=0; i<length; ++i)
  {
    const struct badPhrase *bp;
    const char *badword,*str;

    /* check for bad phrase */ /*{{{*/
    if (phraseStart[(unsigned int)((unsigned char)s[i])] || phraseStart[(unsigned int)((unsigned char)tolower(s[i]))])
    {
      for (j=0; j<badPhraseSize; ++j)
      {
        bp=&badPhrases[j];
        badword=bp->phrase;
        if (*badword==' ')
        {
          if (i && isalpha(s[i-1])) continue;
          ++badword;
        }
        str=&s[i];
        while ((*badword==tolower(*str) || *badword==*str) && *badword && *str) { ++badword; ++str; }
        if (*badword=='\0' && !isalpha(*str))
        {
          if (bp->suggest && *bp->suggest!='!')
          {
            ++hits;
            if (lastout==s) printf("%s:%d: ",file,line);
            while (lastout<s+i) putc(*lastout++,stdout);
            putc('[',stdout);
            while (lastout<str) { putc(*lastout++,stdout); ++i; }
            if (bp->suggest)
            {
              putc(' ',stdout);
              putc('-',stdout);
              putc('>',stdout);
              putc(' ',stdout);
              fputs(bp->suggest,stdout);
            }
            putc(']',stdout);
          }
          else
          {
            while (*badword) ++i;
          }
          break;
        }
      }
    }
    /*}}}*/
    /* check for double words */ /*{{{*/
    if (inword)
    {
      if (isalpha(s[i])) ++wordLength;
      else
      {
        const char *t;
        char *w;
        int last;

        last=wordQueueStart;
        if (++wordQueueStart>=wordQueueLength) wordQueueStart=0;
        if ((wordLength+1)>wordQueue[wordQueueStart].size)
        {
          wordQueue[wordQueueStart].word=realloc(wordQueue[wordQueueStart].word,wordQueue[wordQueueStart].size=wordLength+1);
        }
        w=wordQueue[wordQueueStart].word;
        for (t=s+i-wordLength; t<s+i; ++t,++w) *w=tolower(*t);
        *w++='\0';
        if (wordQueue[last].word && strcmp(wordQueue[last].word,wordQueue[wordQueueStart].word)==0)
        {
          if (lastout==s) printf("%s:%d: ",file,line);
          while (lastout<s+i-wordLength) putc(*lastout++,stdout);
          putc('[',stdout);
          while (lastout<s+i) { putc(*lastout++,stdout); }
          putc(' ',stdout);
          putc('-',stdout);
          putc('>',stdout);
          putc(' ',stdout);
          fputs(DOUBLE,stdout);
          putc(']',stdout);
        }
        inword=0;
      }
    }
    else if (isalpha(s[i]))
    {
      wordLength=1;
      inword=1;
    }
    /*}}}*/
  }
  ++sentences;
  if (lastout!=s)
  {
    while (lastout<s+length) putc(*lastout++,stdout);
    putc('\n',stdout);
    putc('\n',stdout);
  }
}
/*}}}*/

int main(int argc, char *argv[]) /*{{{*/
{
  int usage=0,c;
  const char *phraseLanguage=(const char*)0;
  char *userPhrases=(char*)0;
  char defaultPhrases[_POSIX_PATH_MAX];
  static struct option lopts[]=
  {
    { "file", required_argument, 0, 'f' },
    { "help", no_argument, 0, 'h' },
    { "version", no_argument, 0, 'v' },
    { "language", required_argument, 0, 'L' },
    { "no-default-file", no_argument, 0, 'n' },
    { (const char*)0, 0, 0, '\0' }
  };

  /* init locale */ /*{{{*/
  setlocale(LC_ALL,"");
#if 0
  phraseLanguage=setlocale(LC_MESSAGES,(char*)0);
#else
  if (getenv("LC_MESSAGES")) phraseLanguage=getenv("LC_MESSAGES");
  else phraseLanguage="C";
#endif
  catd=catopen("diction",0);
  /*}}}*/
  /* parse options */ /*{{{*/
  strcpy(defaultPhrases,SHAREDIR "/diction/");
  while ((c=getopt_long(argc,argv,"f:nL:h",lopts,(int*)0))!=EOF) switch(c)
  {
    case 'f': userPhrases=optarg; break;
    case 'n': defaultPhrases[0]='\0'; break;
    case 'L': phraseLanguage=optarg; break;
    case 'v': printf("GNU diction " VERSION "\n"); exit(0);
    case 'h': usage=2; break;
    default: usage=1; break;
  }
  if (defaultPhrases[0]) strcat(defaultPhrases,phraseLanguage);
  if (usage==1 || (userPhrases==(char*)0 && defaultPhrases[0]=='\0'))
  {
    fputs(USAGE1,stderr);
    fputs(USAGE2,stderr);
    fputs(USAGE3,stderr);
    fputs("\n",stderr);
    fputs(MORE,stderr);
    exit(1);
  }
  if (usage==2)
  {
    fputs(USAGE1,stdout);
    fputs(USAGE2,stdout);
    fputs(USAGE3,stdout);
    fputs("\n",stdout);
    fputs(HELP1,stdout);
    fputs("\n",stdout);
    fputs(HELP2,stdout);
    fputs(HELP3,stdout);
    fputs(HELP4,stdout);
    fputs(HELP5,stdout);
    fputs(HELP6,stdout);
    exit(0);
  }
  /*}}}*/
  /* allocate word queue */ /*{{{*/
  if ((wordQueue=malloc(sizeof(struct word)*(wordQueueLength=30)))==(struct word*)0)
  {
    fprintf(stderr,NOMEM);
    exit(2);
  }
  else
  {
    int i;

    for (i=0; i<wordQueueLength; ++i)
    {
      wordQueue[i].word=(char*)0;
      wordQueue[i].size=0;
    }
  }
  /*}}}*/
  if (defaultPhrases[0]) loadPhrases(defaultPhrases);
  if (userPhrases) loadPhrases(userPhrases);
  sentences=0;
  hits=0;
  if (optind==argc) sentence("diction",catd,stdin,"(stdin)",diction,phraseLanguage);
  else while (optind<argc)
  {
    FILE *fp;
    if ((fp=fopen(argv[optind],"r"))==(FILE*)0) fprintf(stderr,NOFILE,argv[optind],strerror(errno));
    {
      sentence("diction",catd,fp,argv[optind],diction,phraseLanguage);
      fclose(fp);
    }
    ++optind;
  }
  if (sentences==0)
  {
    printf(NOSENT);
  }
  else
  {
    if (hits==0) printf(NOPHRASES);
    else if (hits==1) printf(ONEPHRASE);
    else printf(NPHRASES,hits);
    if (sentences==1) printf(ONESENT);
    else printf(NSENT,sentences);
  }
  exit(0);
}
/*}}}*/
