#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#include <time.h>

#include "getopt.h"
#include "wv.h"

/* By Dom Lachowicz (cinamod@hotmail.com) */

/* i don't like appearing to use printf */
#define rtf_output printf
#define rtf_output_char(c)  do {rtf_output("%c", (c));} while(0)
#define ENSURE_BUF()    fflush(stdout)

typedef struct _rtfUserData
{
  /* formatting variables */

  /* cached integer values */
  int cFont;
  int cFontSize;
  int cCol;

  /* boolean formats */
  int bIsBold  :1;
  int bIsItalic:1;
  int bIsStrike:1;
  int bIsUl    :1;
  int bIsSup   :1;
  int bIsSub   :1;

  /* paragraph related */
  int bInPara  :1;  
  int bInSec   :1;
} rtfUserData;

/* this gets printed at the top of each document in the {\fonttbl section */

struct _fontMapping
{
  char *word;
  char *rtf;
};

/* TODO: build me up appropriately */
static struct _fontMapping fontMap[] =
{
  {"Arial",              "Arial"},
  {"Bitstream Charter",  "Bitstream Charter"},
  {"Bookman",            "Bookman"},
  {"Courier",            "Courier"},
  {"Courier New",        "Courier New"},
  {"Century Schoolbook", "Century Schoolbook"},
  {"Dingbats",           "Dingbats"},
  {"Goth",               "Goth"},
  {"Nimbus Sans",        "Nimbus Sans"},
  {"Palladio",           "Palladio"},
  {"Standard Symbol",    "Standard Symbol"},
  {"Symbol",             "Symbol"},
  {"Times",              "Times New Roman"},
  {"Times New Roman",    "Times New Roman"},
};

#define FontTblSize (sizeof(fontMap)/sizeof(fontMap[0]))
#define DFL_FONT_INDEX 13 /* I map this to whatever "Times New Roman" is */
static void
output_fonttable(void)
{
  int i;

  rtf_output("{\\fonttbl\n");

  for(i = 0; i<FontTblSize; i++)
    rtf_output("{\\f%d\\fnil\\fcharset0\\fprq0\\fttruetype %s;}\n", i, fontMap[i].rtf);
  
  rtf_output("}\n");
}

/* map the MSWord name to the corresponding RTF name index */
static int 
mapFont(const char * name)
{
  int k;

  for (k=0; k<FontTblSize; k++)
    if (!strcasecmp(fontMap[k].word, name))
      return k;

  return DFL_FONT_INDEX;
}
#undef DFL_FONT_INDEX
#undef FontTblSize

/* this gets printed at the top of each document in the {\colortbl section */
static int colorTable[][3] = 
{
  {0x00, 0x00, 0x00}, /* black */
  {0x00, 0x00, 0xff}, /* blue */
  {0x00, 0xff, 0xff}, /* cyan */
  {0x00, 0xff, 0x00}, /* green */
  {0xff, 0x00, 0xff}, /* magenta */
  {0xff, 0x00, 0x00}, /* red */
  {0xff, 0xff, 0x00}, /* yellow */
  {0xff, 0xff, 0xff}, /* white */
  {0x00, 0x00, 0x80}, /* dark blue */
  {0x00, 0x80, 0x80}, /* dark cyan */
  {0x00, 0x80, 0x00}, /* dark green */
  {0x80, 0x00, 0x80}, /* dark magenta */
  {0x80, 0x00, 0x00}, /* dark red */
  {0x80, 0x80, 0x00}, /* dark yellow */
  {0x80, 0x80, 0x80}, /* dark gray */
  {0xc0, 0xc0, 0xc0}, /* light gray */
};

/* rtf names for the color_table as above */
static char *rtfColors[] =
{
  "\\cf0", /* black */
  "\\cf1", /* blue */
  "\\cf2", /* cyan */
  "\\cf3", /* green */
  "\\cf4", /* magenta */
  "\\cf5", /* red */
  "\\cf6", /* yellow */
  "\\cf7", /* white */
  "\\cf8", /* dark blue */
  "\\cf9", /* dark cyan */
  "\\cf10", /* dark green */
  "\\cf11", /* dark magenta */
  "\\cf12", /* dark red */
  "\\cf13", /* dark yellow */
  "\\cf14", /* dark gray */
  "\\cf15", /* light gray */
};

#define RED(i)   colorTable[(i)][0]
#define GREEN(i) colorTable[(i)][1]
#define BLUE(i)  colorTable[(i)][2]
#define ClrTblSize (sizeof(colorTable)/sizeof(colorTable[0]))
static void
output_colortable(void)
{
  int i;

  rtf_output("{\\colortbl\n");

  for(i=0;i<ClrTblSize;i++)
    {
      rtf_output("\\red%d\\green%d\\blue%d;\n", RED(i), GREEN(i), BLUE(i));
    }
  
  rtf_output("}\n");
}
#undef RED
#undef GREEN
#undef BLUE
#undef ClrTblSize

static void
output_rtfUserData(rtfUserData *ud)
{
  /* add the initial bracket */
  rtf_output_char('{');

  /* font color */
  rtf_output(rtfColors[ud->cCol]);

  /* font face */
  rtf_output("\\f%d", ud->cFont);

  /* font size */
  rtf_output("\\fs%d", ud->cFontSize);

  /* italic text */
  if (ud->bIsItalic) 
    rtf_output("\\i");

  /* bold text */
  if(ud->bIsBold)
    rtf_output("\\b");

  /* underline and strike-through */
  if(ud->bIsUl)
    rtf_output("\\ul");
  if(ud->bIsStrike)
    rtf_output("\\strike");
    
  /* sub/superscript */
  if (ud->bIsSup) {
    rtf_output("\\super");
  } else if (ud->bIsSub) {
    rtf_output("\\sub");
  }
  /* add the final space */
  rtf_output_char(' ');
}

static void
fill_rtfUserData(rtfUserData *ud, CHP *chp, wvParseStruct *ps)
{
  char *fname = NULL;

  if (!ps->fib.fFarEast) {
    fname = wvGetFontnameFromCode(&ps->fonts, chp->ftcAscii);
  } else {
    fname = wvGetFontnameFromCode(&ps->fonts, chp->ftcFE);
  }

  ud->cCol      = ((chp->ico) ? (chp->ico - 1) : 0);
  ud->cFont     = mapFont(fname);
  ud->cFontSize = chp->hps;
  ud->bIsBold   = (chp->fBold);
  ud->bIsItalic = (chp->fItalic);
  ud->bIsUl     = (chp->kul);
  ud->bIsStrike = (chp->fStrike);
  ud->bIsSup    = (chp->iss == 1);
  ud->bIsSub    = (chp->iss == 2);
}

static void
handleImage(Blip * b, long width, long height)
{

  /* TODO: image support */
  FILE *fd = NULL;
  int data = 0;
  int cnt = 0;
  int tag = time(NULL);

  /* short-circuit this method if we don't support
     the incoming format */
  switch(b->type)
    {
    case msoblipPNG:
      /* conveniently I know how to export to PNG */
      rtf_output("{\\*\\shppict{\\pict\\pngblip\\picw%d\\pich%d\\picwgoal\\pichgoal\n", width, height);
      break;
    case msoblipDIB:
    case msoblipWMF:
    case msoblipEMF:
    case msoblipPICT:
    case msoblipJPEG:
    default:
      /* TODO: support other image types */
      return;
    }

  rtf_output("\bliptag%d{\\*\\blipuid%032x}", tag, tag);
  
  fd = (FILE*)(b->blip.bitmap.m_pvBits);
  while (EOF != (data = getc(fd)))
    {
      if (cnt++ % 64 == 0)
	rtf_output_char('\n');
      rtf_output("%02x", data);
    }

  rtf_output_char('}');
}

static int 
charProc(wvParseStruct *ps, U16 eachchar, U8 chartype, U16 lid)
{

  /* convert incoming character to unicode */
  if (chartype)
    eachchar = wvHandleCodePage(eachchar, lid);

  /* take care of any oddities in Microsoft's character "encoding" */
  /* TODO: does the above code page handler take care of these? */
  if (chartype == 1 && eachchar == 146) eachchar = 39; /* apostrophe */

  switch (eachchar)
    {
    case 13: /* paragraph end */
      return 0;

    case 11: /* hard line break */
      break;

    case 12: /* page breaks, section marks */
      break;

    case 14: /* column break */
      break;

    case 19: /* field begin */
      /* flush current text buffer */
      ps->fieldstate++;
      ps->fieldmiddle = 0;
      return 0;
    case 20: /* field separator */
      ps->fieldmiddle = 1;
      return 0;
    case 21: /* field end */
      ps->fieldstate--;
      ps->fieldmiddle = 0;
      return 0;

    default:
      break;
    }

  /* todo: properly handle fields */
  if (eachchar == 0x13 || eachchar == 0x14) 
    return 0;

  /* properly escape this */
  if(eachchar == '{' || eachchar == '}')
    rtf_output_char('\\');

  rtf_output_char(eachchar);
  return 0;
}

static int 
specCharProc(wvParseStruct *ps, U16 eachchar, CHP * achp)
{
  Blip blip;
  wvStream *fil;       
  long pos;
  FSPA * fspa;
  PICF picf;
  FDOA * fdoa;
  
  switch(eachchar)
    {
    case 19: /* field begin */
      ps->fieldstate++;
      ps->fieldmiddle = 0;
      return 0;
    case 20: /* field separator */
      if (achp->fOle2)
	{
	  wvTrace(("Field has an embedded OLE2 object\n"));
	}
      ps->fieldmiddle = 1;
      return 0;
    case 21: /* field end */
      ps->fieldstate--;
      ps->fieldmiddle = 0;
      return 0;
    default:
      break;
    }

  /* TODO: properly handle fields */
  if(ps->fieldstate)
    {
      if (eachchar == 0x13 || eachchar == 0x14) 
	return 0;
    }

  /* image handling */
  switch (eachchar) 
    {
    case 0x01:
      
      if (achp->fOle2)
	{
	  wvTrace(("embedded OLE2 component. currently unsupported"));
	  return 0;
	}
         
      pos = wvStream_tell(ps->data);
      
      wvStream_goto(ps->data, achp->fcPic_fcObj_lTagObj);
      
      wvGetPICF(wvQuerySupported(&ps->fib, NULL), &picf, ps->data);
      
      fil = picf.rgb;
      
      if (wv0x01(&blip, fil, picf.lcb - picf.cbHeader))
	{
	  handleImage(&blip, picf.dxaGoal, picf.dyaGoal);
	}
      else
	{
	  wvTrace(("Dom: strange no graphic data 1\n"));
	}
 
      wvStream_goto(ps->data, pos);
      
      return 0;
      break;
         
    case 0x08:
      
      if (wvQuerySupported(&ps->fib, NULL) == WORD8)
	{
	  if(ps->nooffspa>0)
	    {
	      
	      fspa = wvGetFSPAFromCP(ps->currentcp, ps->fspa,
				     ps->fspapos, ps->nooffspa);
	      
	      if(!fspa)
		{
		  wvError(("No fspa! Panic and Insanity Abounds!\n"));
		  return 0;
		}          
 
	      if (wv0x08(&blip, fspa->spid, ps))
		{
		  handleImage(&blip, fspa->xaRight-fspa->xaLeft,
			      fspa->yaBottom-fspa->yaTop);
		}
	      else
		{
		  wvTrace(("Dom: strange no graphic data 2\n"));
		  return 0;
		}
	    }
	  else
             {
               wvTrace(("nooffspa was <=0 -- ignoring"));
             } 
           }
      else
	{
	  wvError(("pre Word8 0x08 graphic -- unsupported at the moment"));
	  fdoa = wvGetFDOAFromCP(ps->currentcp, NULL, ps->fdoapos, ps->nooffdoa);
	}
      
    }

  return 0;
}

static int 
eleProc(wvParseStruct *ps, wvTag tag, void *props, int dirty)
{
  /* some word structures */
  PAP *apap;
  CHP *achp;
  SEP *asep;
  int iRes;

  rtfUserData *ud = (rtfUserData*)ps->userData;

  switch(tag)
    {
    case SECTIONBEGIN:

      /* TODO: get smarter */
      asep = (SEP*)props;
      rtf_output("\\sectd\\sbknone\\colsx360\n");

      ud->bInSec = 1;
      break;

    case SECTIONEND:
      ud->bInSec = 0;
      break;

    case PARABEGIN:
      apap = (PAP*)props;

      ud->bInPara = 1;

      rtf_output("\\pard");
      switch(apap->jc)
	{
	case 0: /* left */
	  break;
	case 1: /* center */
	  rtf_output("\\qc");
	  break;
	case 2: /* right */
	  rtf_output("\\qr");
	  break;
	default:
	  break;
	}

      break;

    case PARAEND: /* pretty much nothing */
      rtf_output_char('\n');
      ud->bInPara = 0;
      break;

    case CHARPROPBEGIN:
      achp = (CHP*)props;
      fill_rtfUserData(ud, achp, ps);
      output_rtfUserData(ud);
      break;
      
    case CHARPROPEND:
      achp = (CHP*)props;
      fill_rtfUserData(ud, achp, ps);
      if(ud->bInPara)
	{
	  rtf_output_char('}');
	}
      break;

    default:
      break;
    }

  return 0;
}

static int 
docProc(wvParseStruct *ps, wvTag tag)
{
  switch(tag)
    {
    case DOCBEGIN:
      /* print out my rtf preamble */
      rtf_output("{\\rtf1\\ansi\\ansicpg1252\\deff0\n");
      
      /* now print out a font table */
      /* and a color table */
      output_fonttable();
      output_colortable();
      rtf_output("\\kerning0\\cf0\\viewkind1\\paperw12240\\paperh15840\\margl1440\\margr1440\\widowctl\n");
      break;

    case DOCEND:
      rtf_output("}\n");
      ENSURE_BUF();
      break;

    default:
      break;
    }

  return 0;
}

static void
do_version(void)
{
  printf("wvRTF version 0.6.5\n");
}

static void
do_help(void)
{
  do_version();
  printf("(c) Dom Lachowicz 2000\n");
  printf("Usage:\n");
  printf("\t-c --charset=set\n");
  printf("\t-p --password=pass\n");
  printf("\t-v --version\n");
  printf("\t-? --help\n");
  printf("\nConverts MSWord documents to RTF\n");
}

static char *charset=NULL;

int
main(int argc, char *argv[])
{
  FILE *input;
  char *fname, *password;
  int   ret;

  wvParseStruct ps;
  char *dir=NULL;

  rtfUserData ud;

  static struct option long_options[] =
  {
    {"charset",1,0,'c'},
    {"password",1,0,'p'},
    {"dir",1,0,'d'},
    {"version",0,0,'v'},
    {"help",0,0,'?'},
    {0,0,0,0}
  };

  int c, index=0;

  wvInitError();
  
  if(argc < 2)
    {
      do_help();
      return 1;
    }

  while (1)
    { 
      c = getopt_long (argc, argv, "?vc:p:d:", long_options, &index);
      if (c == -1)
	break;
      switch(c)
	{
	case '?':
	  do_help();
	  return 0;
	case 'v':
	  do_version();
	  return 0;
	case 'c':
	  if (optarg)
	    charset = optarg;
	  else
	    wvError(("No argument given to charset"));
	  break;
	case 'p':
	  if (optarg)
	    password = optarg;
	  else
	    wvError(("No password given to password option"));
	  break;
	case 'd':
	  if (optarg)
	    dir = optarg;
	  else
	    wvError(("No directory given to dir option"));
	  break;
	default:
	  do_help();
	  return -1;
	}
    }

  if (optind >= argc)
    {
      fprintf(stderr,"No file name given to open\n");
      return -1;
    }

  fname = argv[optind];

  input = fopen(fname,"rb");
  if (!input)
    {
      fprintf(stderr,"Failed to open %s: %s\n", fname, strerror(errno));
      return -1;
    }
  fclose(input);
  
  ret = wvInitParser(&ps,fname);
  ps.filename = fname;
  ps.dir = dir;
  
  /* set to 0 */
  memset(&ud, 1, sizeof(rtfUserData));
  ps.userData = &ud;

  if (ret & 0x8000)  /* Password protected? */
    {
      if ( (ret & 0x7fff) == WORD8)
	{
	  ret = 0;
	  if (password == NULL)
	    {
	      fprintf(stderr,"Password required, this is an encrypted document\n");
	      return -1;
	    }
	  else
	    {
	      wvSetPassword(password,&ps);
	      if (wvDecrypt97(&ps))
		{
		  wvError(("Incorrect Password\n"));
		  return -1;
		}
	    }
	}
      else if ( ( (ret & 0x7fff) == WORD7) || ( (ret & 0x7fff) == WORD6))
	{
	  ret=0;
	  if (password == NULL)
	    {
	      fprintf(stderr,"Password required, this is an encrypted document\n");
	      return -1;
	    }
	  else
	    {
	      wvSetPassword(password,&ps);
	      if (wvDecrypt95(&ps))
		{
		  wvError(("Incorrect Password\n"));
		  return -1;
		}
	    }
	}
    }

  if (ret)
    {
      wvError(("startup error\n"));
      wvOLEFree();
      return -1;
    }

  wvSetElementHandler(eleProc);
  wvSetDocumentHandler(docProc);
  wvSetCharHandler(charProc);
  wvSetSpecialCharHandler(specCharProc);

  wvText(&ps);

  /* free associated memory */
  wvOLEFree();

  return 0;
}
