/* for now, just for testing purposes */
#define DEBUG 1

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

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

#ifdef HAVE_MALLOC_H
#include <malloc.h>
#endif

#include "wvexporter-priv.h"
#include "ms-ole-summary.h"

  /* the temporary storage struct for our summay data 
   * for important reasons, this gets written out
   * at the end of the export, after the other streams
   * are closed
   */
  typedef struct _OleSummaryData OleSummaryData;

  struct _OleSummaryData {
    char *  title;
    char *  subject;
    char *  author;
    char *  keywords;
    char *  comments;
    char *  template;
    char *  lastauthor;
    char *  revnumber;
    char *  appname;

    U32     pagecount;
    U32     wordcount;
    U32     charcount;
    U32     security;
    U32     thumbnail;

    time_t  total_edittime;
    time_t  lastprinted;
    time_t  created;
    time_t  lastsaved;
  };

  struct _wvExporter {

    /* consider all of this data to be private 
     * the only reliable way to get/manipulate
     * data is through the exposed API
     */

    /* our toplevel OLE Document 
     * from which all of our following streams
     * are created
     */
    wvDocument      *ole;

    /* the open OLE streams within the toplevel OLE document */
    wvStream        *documentStream;
    wvStream        *table1Stream;
    wvStream        *table0Stream;
    wvStream        *dataStream;

    /* more accounting structures */

    OleSummaryData  * summary;
    wvVersion       ver;
    FIB             fib;
  };

/* document stream names */
#define DOCUMENT_STREAM  "WordDocument"
#define TABLE1_STREAM    "1Table"
#define TABLE0_STREAM    "0Table"
#define DATA_STREAM      "Data"

#define ASSIGN_STRING(a, b) assign_string(&a, b)
#define SUMMARY_SET_STRING(a, b, c) if(c) ms_ole_summary_set_string (a, b, c)

#define ASSERT_STREAM_CREATED(s) \
if(!s) { \
  wvError(("Error creating %s stream\n", #s));\
  return NULL;\
}

#define WVSTREAM_CLOSE(s) \
  ms_ole_stream_close(&((s)->stream.libole_stream))

/* some static functions */

static void assign_string (char **a, const char *b);
static void exporter_close_word8(wvExporter *exp);
static wvExporter * exporter_create_word8(const char *filename);
static void write_ole_summary(OleSummaryData * data, MsOleSummary * strm);

/**************************************************************************/
/**                   Public implementation functions                    **/
/**************************************************************************/


/**
 * wvExporter_queryVerionSupported
 *
 * @returns 1 if we can export to version # @v
 * Of the MSWord format or 0 if not
 *
 * Currently Supported Versions: WORD8
 *
 * @v - version
 */
S8
wvExporter_queryVersionSupported(wvVersion v)
{
  switch(v){
  case WORD8:
    return 1;
  default:
    return 0;
  }
}

/**
 * wvExporter_getVersion
 *
 * @exp - a valid exporter created by wvExporter_create
 *
 * @returns the version type of the wvExporter @exp, 0 on error
 */
wvVersion
wvExporter_getVersion(wvExporter *exp)
{
  if(!exp)
    {
      wvError(("Attempt to get version from a NULL exporter\n"));
      return (wvVersion)0;
    }
  return exp->ver;
}

/**
 * wvExporter_create_version
 *
 * Creates a MSWord exporter object, with the version @v
 * If version @v isn't supported, return NULL. Call
 * <code>wvExporter_queryVersionSupported(@v)</code> to see
 * If @v is a supported version
 *
 * @filename - file on disk to create 
 * @v - version of Word Format to create. Only valid for
 *      versions where wvExporter_queryVersionSupporter (@v) == 1
 *
 * @returns <code>NULL</code> on error, or a valid wvExporter on success
 */
wvExporter *
wvExporter_create_version(const char *filename, wvVersion v)
{
  if(!wvExporter_queryVersionSupported(v))
    {
      wvError(("wvExporter: unsupported version Word%d", (int)v));
      return NULL;
    }

  switch (v) {
  case WORD8: 
    return exporter_create_word8(filename);
  default:
    wvError(("Cannot create unsupported version: %d\n", (int)v));
    return NULL;
  }
}

/**
 * wvExporter_create
 *
 * Creates a Word exporter object. Guaranteed to return 
 * an exporter for the most recent supported MSWord DOC version.
 * This means that this function currently creates a Word97/2000
 * exporter object (WORD8)
 *
 * @filename - file on disk to create 
 * @returns <code>NULL</code> on error, or a valid wvExporter on success
 */
wvExporter * 
wvExporter_create(const char *filename)
{
  return wvExporter_create_version(filename, WORD8);
}

/**
 * wvExporter_close
 *
 * Closes and saves the MSWord document
 *
 * @exp - an exporter created by wvExporter_create()
 */
void
wvExporter_close(wvExporter *exp)
{
  if(exp == NULL) {
    wvError(("Exporter can't be null\n"));
    return;
  }

  switch (wvExporter_getVersion(exp))
    {
    case WORD8:
      exporter_close_word8(exp);
      break;
      
    default:
      wvError(("Closing wvExporter with an invalid version\n"));      
      break;
    }

  wvTrace(("Word Document Written!\n"));
}

/**************************************************************************/
/** All of the wvExporter_summaryXXX functions share 1 common            **/
/** Characteristic: only the final call for any given value is of any    **/
/** Importance. This means that one could call                           **/
/** wvExporter_summaryPutString() for PID_TITLE 35 times, but only the   **/
/** Final call means anything                                            **/
/**************************************************************************/

/**
 * wvExporter_summaryPutString
 *
 * @exp - a valid wvExporter created by wvExporter_create()
 * @field - summary stream id key PID_XXX from "wv.h"
 * @str - string to put into the summary stream
 *
 * If field isn't a valid value or isn't valid for the given
 * type (i.e. string), this function will return 0
 *
 * @returns 1 on success, 0 on failure
 */
S8 
wvExporter_summaryPutString(wvExporter *exp, U32 field, const char *str)
{
  if(exp == NULL){
    wvError(("Exporter can't be null\n"));
    return 0;
  }
  if(str == NULL){
    wvError(("String can't be null\n"));
    return 0;
  }

  switch(field)
    {
      /* summary stream */
      case PID_TITLE:      ASSIGN_STRING(exp->summary->title, str);      break;
      case PID_SUBJECT:    ASSIGN_STRING(exp->summary->subject, str);    break;
      case PID_AUTHOR:     ASSIGN_STRING(exp->summary->author, str);     break;
      case PID_KEYWORDS:   ASSIGN_STRING(exp->summary->keywords, str);   break;
      case PID_COMMENTS:   ASSIGN_STRING(exp->summary->comments, str);   break;
      case PID_TEMPLATE:   ASSIGN_STRING(exp->summary->template, str);   break;
      case PID_LASTAUTHOR: ASSIGN_STRING(exp->summary->lastauthor, str); break;
      case PID_REVNUMBER:  ASSIGN_STRING(exp->summary->revnumber, str);  break;
      case PID_APPNAME:    ASSIGN_STRING(exp->summary->appname, str);    break;
    default:
      wvError(("Unhandled type: %d\n", field));
      return 0;
    }

  return 1;
}

/**
 * wvExporter_summaryPutLong
 *
 * @exp - a valid wvExporter created by wvExporter_create()
 * @field - summary stream id key PID_XXX from "wv.h"
 * @l - long value
 *
 * If field isn't a valid value or isn't valid for the given
 * type (i.e. long), this function will return 0
 *
 * @returns 1 on success, 0 on failure
 */
S8
wvExporter_summaryPutLong(wvExporter *exp, U32 field, U32 l)
{
  if(exp == NULL){
    wvError(("Exporter can't be null\n"));
    return 0;
  }

  switch(field)
    {
      /* summary stream */
    case PID_PAGECOUNT: exp->summary->pagecount = l; break;  
    case PID_WORDCOUNT: exp->summary->wordcount = l; break;
    case PID_CHARCOUNT: exp->summary->charcount = l; break;
    case PID_SECURITY:  exp->summary->security  = l;  break;
    case PID_THUMBNAIL: exp->summary->thumbnail = l; break;

    default:
      wvError(("Unhandled type: %d\n", field));
      return 0;
    }

  return 1;
}

/**
 * wvExporter_summaryPutTime
 *
 * @exp - a valid wvExporter created by wvExporter_create()
 * @field - summary stream id key PID_XXX from "wv.h"
 * @t - UNIX time_t value
 *
 * If @field isn't a valid value or isn't valid for the given
 * type (i.e. time_t), this function will return 0
 *
 * @returns 1 on success, 0 on failure
 */
S8
wvExporter_summaryPutTime(wvExporter *exp, U32 field, time_t t)
{
  if(exp == NULL) {
    wvError(("Exporter can't be null\n"));
    return 0;
  }

  switch(field)
    {
      /* summary stream only */
    case PID_TOTAL_EDITTIME: exp->summary->total_edittime = t;  break;
    case PID_LASTPRINTED:    exp->summary->lastprinted    = t;  break;
    case PID_CREATED:        exp->summary->created        = t;  break;
    case PID_LASTSAVED:      exp->summary->lastsaved      = t;  break;
    default:
      wvError(("Unhandled type: %d\n", field));
      return 0;
    }

  return 1;
}

/**************************************************************************/
/**************************************************************************/

/**
 * wvExporter_writeChars
 *
 * If you're worried, use wvExporter_writeBytes instead.
 *
 * Writes the string @chars to the Word document @exp
 * You should be passing UTF8 here
 *
 * @returns number of chars written
 */
size_t
wvExporter_writeChars(wvExporter *exp, const U8 *chars)
{
  if(exp == NULL) {
    wvError(("Exporter can't be NULL\n"));
    return 0;
  }
  if(chars == NULL) {
    wvError(("I won't write a NULL string\n"));
    return 0;
  }

  return wvExporter_writeBytes(exp, sizeof(U8), strlen(chars), 
			       (const void *)chars);
}

/**
 * wvExporter_writeBytes
 *
 * Should be UTF-safe
 *
 * Writes @nmemb members from the array of @bytes of 
 * in size @sz chunks to the @exp word document
 *
 * @returns number of bytes written
 */
size_t
wvExporter_writeBytes(wvExporter *exp, size_t sz, size_t nmemb,
		      const void *bytes)
{
  size_t nwr = 0;

  if(exp == NULL) {
    wvError(("Exporter can't be NULL\n"));
    return 0;
  }
  if(sz == 0) {
    wvError(("Attempting to write an array of zero size items? WTF?\n"));
    return 0;
  }
  if(nmemb == 0) {
    /* not so bad I guess */
    wvTrace(("Zero bytes passed to writeBytes\n"));
    return 0;
  }
  if(bytes == 0) {
    /* TODO: is this an error? */
    wvTrace(("NULL array passed to writeBytes\n"));
    return 0;
  }

  /* write the bytes and update the FIB */
  nwr = wvStream_write((void *)bytes, sz, nmemb, exp->documentStream);
  exp->fib.fcMac = wvStream_tell(exp->documentStream) + 1;

  wvTrace(("Wrote %d byte(s)\n", nwr));
  return nwr;
}

/**
 * wvExporter_flush
 *
 * Flushes any data possibly stored in the exporter's
 * Internal buffers.
 *
 * @exp - an exporter created by wvExporter_create
 */
void
wvExporter_flush(wvExporter *exp)
{
  if(!exp)
    {
      wvError(("Cannot flush a null exporter object\n"));
      return;
    }
  /* this will be a noop indefinitely */
}

/**************************************************************************/
/**************************************************************************/

/**
 * wvExporter_pushPAP
 *
 * This function closes any previous paragraph, if any are
 * Open, and begins a new paragraph with the properties
 * Contained in @apap
 *
 * @returns 1 on success, 0 if not
 */
S8
wvExporter_pushPAP(wvExporter *exp, PAP *apap)
{
  if(!exp)
    {
      wvError(("NULL exporter\n"));
      return 0;
    }

  if(!apap)
    {
      wvError(("NULL PAP!\n"));
      return 0;
    }

  /* noop for now */

  return 1;
}

/**
 * wvExporter_pushCHP
 *
 * This function closes any previous character run, if any are
 * Open, and begins a new character run with the properties
 * Contained in @achp
 *
 * @returns 1 on success, 0 if not
 */
S8
wvExporter_pushCHP(wvExporter *exp, CHP *achp)
{
  if(!exp)
    {
      wvError(("NULL exporter\n"));
      return 0;
    }

  if(!achp)
    {
      wvError(("NULL CHP!\n"));
      return 0;
    }

  /* noop for now */

  return 1;
}

/**
 * wvExporter_pushSEP
 *
 * This function closes any previous section, if any are
 * Open, and begins a new section with the properties
 * Contained in @asep
 *
 * @returns 1 on success, 0 if not
 */
S8
wvExporter_pushSEP(wvExporter *exp, SEP *asep)
{
  if(!exp)
    {
      wvError(("NULL exporter\n"));
      return 0;
    }

  if(!asep)
    {
      wvError(("NULL SEP!\n"));
      return 0;
    }

  /* noop for now */

  return 1;
}


/**************************************************************************/
/**                   Static implementation functions                    **/
/**************************************************************************/

static void
assign_string (char **a, const char *b)
{
  int len = 0;

  if(!b)
    return;

  if(*a)
    wvFree(*a);
  
  len = strlen (b);
  (*a) = (char *)wvMalloc(sizeof(char) * (len + 1));
  strcpy(*a, b);
  (*a) [len] = 0;
}

static void
write_ole_summary(OleSummaryData * data, MsOleSummary * sum)
{
  SUMMARY_SET_STRING (sum, MS_OLE_SUMMARY_TITLE,      data->title);
  SUMMARY_SET_STRING (sum, MS_OLE_SUMMARY_SUBJECT,    data->subject);
  SUMMARY_SET_STRING (sum, MS_OLE_SUMMARY_AUTHOR,     data->author);
  SUMMARY_SET_STRING (sum, MS_OLE_SUMMARY_KEYWORDS,   data->keywords);
  SUMMARY_SET_STRING (sum, MS_OLE_SUMMARY_COMMENTS,   data->comments);
  SUMMARY_SET_STRING (sum, MS_OLE_SUMMARY_TEMPLATE,   data->template);
  SUMMARY_SET_STRING (sum, MS_OLE_SUMMARY_LASTAUTHOR, data->lastauthor);
  SUMMARY_SET_STRING (sum, MS_OLE_SUMMARY_REVNUMBER,  data->revnumber);
  SUMMARY_SET_STRING (sum, MS_OLE_SUMMARY_APPNAME,    data->appname);

  ms_ole_summary_set_long (sum, MS_OLE_SUMMARY_PAGECOUNT, data->pagecount);
  ms_ole_summary_set_long (sum, MS_OLE_SUMMARY_WORDCOUNT, data->wordcount);
  ms_ole_summary_set_long (sum, MS_OLE_SUMMARY_CHARCOUNT, data->charcount);
  ms_ole_summary_set_long (sum, MS_OLE_SUMMARY_SECURITY,  data->security);
  ms_ole_summary_set_long (sum, MS_OLE_SUMMARY_THUMBNAIL, data->thumbnail);

#if 0
  /* TODO: the time types are not currently supported */
#endif
}

static wvExporter *
exporter_create_word8(const char *filename)
{
  wvExporter *exp = NULL;
  MsOle *ole = NULL;

  if(filename == NULL) {
    wvError(("Error: file name can't be null\n"));
    return NULL;
  }

  /* first allocate the exporter object, initialized to 0's */  
  exp = (wvExporter *)calloc(1, sizeof(wvExporter));
  if(!exp) {
    wvError(("Error allocating memory for the exporter\n"));
    return NULL;
  }

  if(ms_ole_create((MsOle**)(&ole), filename) != MS_OLE_ERR_OK)
    {
      wvError(("Error creating OLE docfile %s\n", filename)); 
      wvFree(ole);
      wvFree(exp);
      return NULL;
    }

  wvTrace(("Created OLE\n"));
  exp->ole = (wvDocument *)ole;
  
  /* now to initialize the streams */

  exp->documentStream = wvStream_new(ole, DOCUMENT_STREAM); 
  ASSERT_STREAM_CREATED(exp->documentStream);

  exp->table0Stream = wvStream_new(ole, TABLE0_STREAM);
  ASSERT_STREAM_CREATED(exp->table0Stream);

  exp->table1Stream = wvStream_new(ole, TABLE1_STREAM);
  ASSERT_STREAM_CREATED(exp->table1Stream);

  exp->dataStream = wvStream_new(ole, DATA_STREAM);
  ASSERT_STREAM_CREATED(exp->dataStream);

  wvTrace(("Created all relevant OLE streams\n"));
  
  /* initialize the FIB and put it into the document stream
   * this is fine to do, since we're going to rewind the
   * stream, and put an updated FIB in the stream on
   * wvExporter_close() anyway
   */
  wvInitFIBForExport(&(exp->fib));
  wvPutFIB(&(exp->fib), exp->documentStream);
  wvTrace(("Initial FIB inserted at: %d (%d)\n", wvStream_tell(exp->documentStream), 
	   (wvStream_tell(exp->documentStream) - sizeof(FIB))));
    
  /* in all of the document's i've run into, the fcMin == 1024 */
  exp->fib.fcMin = wvStream_tell(exp->documentStream);

  exp->ver = WORD8;
  exp->summary = (OleSummaryData *)calloc(1, sizeof(OleSummaryData));
  return exp;
}

static void
exporter_close_word8(wvExporter *exp)
{
  MsOleSummary *doc, *sum;

  wvExporter_flush(exp);
  
  /* rewind and put the updated FIB in its proper place */

  /* last character's position + 1 */
  /* exp->fib.cbMac = wvStream_tell(exp->documentStream) + 1; */
  exp->fib.ccpText = exp->fib.cbMac - exp->fib.fcMin;

  wvStream_rewind(exp->documentStream);
  wvPutFIB(&(exp->fib), exp->documentStream);
  wvTrace(("Re-inserted FIB into document at: %d\n", 
	   wvStream_tell(exp->documentStream)));

  /*
   * Close all of the streams
   * TODO: make this a function instead of a macro
   */
  WVSTREAM_CLOSE((exp->documentStream));
  WVSTREAM_CLOSE((exp->table1Stream));
  WVSTREAM_CLOSE((exp->table0Stream));
  WVSTREAM_CLOSE((exp->dataStream));
  wvTrace(("Closed all of the main streams\n"));

  /*
   * Close the summary streams
   */
  sum = ms_ole_summary_create (exp->ole);
  write_ole_summary (exp->summary, sum);
  ms_ole_summary_close (sum);

  wvTrace(("Wrote summary stream(s)\n"));

  /* close the document */
  ms_ole_destroy(&(exp->ole));
  wvTrace(("Closed all of the streams and OLE\n"));

  wvFree(exp->summary);
  wvFree(exp);
  exp = NULL;
}
