/******************************************************************************
 *
 * 
 *
 * Copyright (C) 1997-2000 by Dimitri van Heesch.
 *
 * Permission to use, copy, modify, and distribute this software and its
 * documentation under the terms of the GNU General Public License is hereby 
 * granted. No representations are made about the suitability of this software 
 * for any purpose. It is provided "as is" without express or implied warranty.
 * See the GNU General Public License for more details.
 *
 * Documents produced by Doxygen are derivative works derived from the
 * input used in their production; they are not affected by this license.
 *
 * The code is this file is largely based on a contribution from
 * Harm van der Heijden <H.v.d.Heijden@phys.tue.nl>
 * Please send thanks to him and bug reports to me :-)
 */

#include <stdio.h>
#include <stdlib.h>
#include <qlist.h>
#include <qdict.h>

#include "htmlhelp.h"
#include "config.h"
#include "message.h"

//----------------------------------------------------------------------------

struct IndexField
{
  QCString name;
  QCString url;
  QCString anchor;
  bool     link;
};

class IndexFieldList : public QList<IndexField>
{
  public:
    int compareItems(GCI item1, GCI item2)
    {
      return stricmp(((IndexField *)item1)->name,((IndexField *)item2)->name);
    }
   ~IndexFieldList() {}
};

class IndexFieldListIterator : public QListIterator<IndexField>
{
  public:
    IndexFieldListIterator( const IndexFieldList &list) :
       QListIterator<IndexField>(list) {}
};

class IndexFieldDict : public QDict<IndexField>
{
  public:
    IndexFieldDict(int size) : QDict<IndexField>(size) {}
   ~IndexFieldDict() {}
};

/*! A helper class for HtmlHelp that manages a two level index in 
 * alphabetical order 
 */
class HtmlHelpIndex
{
  public:
    HtmlHelpIndex();
   ~HtmlHelpIndex();
    void addItem(const char *first,const char *second, 
                 const char *url, const char *anchor,bool hasLink);
    void writeFields(QTextStream &t);
  private:
    IndexFieldList *list;
    IndexFieldDict *dict;   
};

/*! Constructs a new HtmlHelp index */
HtmlHelpIndex::HtmlHelpIndex()
{
  list = new IndexFieldList;
  dict = new IndexFieldDict(10007);
  list->setAutoDelete(TRUE);
}

/*! Destroys the HtmlHelp index */
HtmlHelpIndex::~HtmlHelpIndex()
{
  delete list;
  delete dict;
}

/*! Stores an item in the index if it is not already present. 
 *  Items are stored in alphetical order, by sorting on the
 *  concatenation of \a level1 and \a level2 (if present).
 *
 *  \param level1 the string at level 1 in the index.
 *  \param level2 the string at level 2 in the index (or 0 if not applicable).
 *  \param url the url of the documentation (without .html extension).
 *  \param anchor the anchor of the documentation within the page.
 *  \param hasLink if true, the url (without anchor) can be used in the 
 *         level1 item, when writing the header of a list of level2 items.
 */
void HtmlHelpIndex::addItem(const char *level1,const char *level2,
                       const char *url,const char *anchor,bool hasLink)
{
  QCString key = level1; 
  if (level2) key+= (QCString)"?" + level2;
  if (dict->find(key)==0) // new key
  {
    //printf(">>>>>>>>> HtmlHelpIndex::addItem(%s,%s,%s,%s)\n",
    //      level1,level2,url,anchor);
    IndexField *f = new IndexField;
    f->name   = key;
    f->url    = url;
    f->anchor = anchor;
    f->link   = hasLink;
    list->inSort(f);
    dict->insert(key,f);
  }
}

/*! Writes the sorted list of index items into a html like list.
 *
 *  An list of calls with <code>name = level1,level2</code> as follows:
 *  <pre>
 *    a1,b1
 *    a1,b2
 *    a2,b1
 *    a2,b2
 *    a3
 *    a4,b1
 *  </pre>
 *
 *  Will result in the following list:
 *
 *  <pre>
 *    a1       -> link to url if hasLink==TRUE
 *      b1     -> link to url#anchor
 *      b2     -> link to url#anchor
 *    a2       -> link to url if hasLink==TRUE
 *      b1     -> link to url#anchor
 *      b2     -> link to url#anchor
 *    a3       -> link to url if hasLink==TRUE
 *    a4       -> link to url if hasLink==TRUE
 *      b1     -> link to url#anchor 
 *  </pre>
 */
void HtmlHelpIndex::writeFields(QTextStream &t)
{
  IndexFieldListIterator ifli(*list);
  IndexField *f;
  QCString lastLevel1;
  bool level2Started=FALSE;
  for (;(f=ifli.current());++ifli)
  {
    QCString level1,level2;
    int i;
    if ((i=f->name.find('?'))!=-1)
    {
      level1 = f->name.left(i);
      level2 = f->name.right(f->name.length()-i-1); 
    }
    else
    {
      level1  = f->name.copy();
    }

    if (level1!=lastLevel1)
    { // finish old list at level 2
      if (level2Started) t << "  </UL>" << endl;
      level2Started=FALSE;
    
      if (level2.isEmpty())
      {
        t << "  <LI><OBJECT type=\"text/sitemap\">";
        t << "<param name=\"Local\" value=\"" << f->url << ".html";
        if (!f->anchor.isEmpty()) t << "#" << f->anchor;  
        t << "\">";
        t << "<param name=\"Name\" value=\"" << level1 << "\">"
           "</OBJECT>\n";
      }
      else
      {
        if (f->link)
        {
          t << "  <LI><OBJECT type=\"text/sitemap\">";
          t << "<param name=\"Local\" value=\"" << f->url << ".html\">";
          t << "<param name=\"Name\" value=\"" << level1 << "\">"
               "</OBJECT>\n";
        }
        else
        {
          t << "  <LI><OBJECT type=\"text/sitemap\">";
          t << "<param name=\"See Also\" value=\"" << level1 << "\">";
          t << "<param name=\"Name\" value=\"" << level1 << "\">"
               "</OBJECT>\n";
        }
      }
    }
    if (!level2Started && !level2.isEmpty())
    { // start new list at level 2
      t << "  <UL>" << endl;
      level2Started=TRUE;
    }
    else if (level2Started && level2.isEmpty())
    { // end list at level 2
      t << "  </UL>" << endl;
      level2Started=FALSE;
    }
    if (level2Started)
    {
      t << "    <LI><OBJECT type=\"text/sitemap\">";
      t << "<param name=\"Local\" value=\"" << f->url << ".html";
      if (!f->anchor.isEmpty()) t << "#" << f->anchor;  
      t << "\">";
      t << "<param name=\"Name\" value=\"" << level2 << "\">"
         "</OBJECT>\n";
    }
    lastLevel1 = level1.copy();
  } 
}

//----------------------------------------------------------------------------

HtmlHelp *HtmlHelp::theInstance = 0;

/*! Constructs an html object. 
 *  The object has to be \link initialize() initialized\endlink before it can 
 *  be used.
 */
HtmlHelp::HtmlHelp()
{
  /* initial depth */
  dc = 0;
  cf = kf = 0;
  index = new HtmlHelpIndex;
}

/*! return a reference to the one and only instance of this class. 
 */
HtmlHelp *HtmlHelp::getInstance()
{
  if (theInstance==0) theInstance = new HtmlHelp;
  return theInstance;
}

/*! This will create a contents file (index.hhc) and a index file (index.hhk)
 *  and write the header of those files. 
 *  It also creates a project file (index.hhp)
 *  \sa finalize()
 */
void HtmlHelp::initialize()
{
  /* open the contents file */
  QCString fName = Config::htmlOutputDir + "/index.hhc";
  cf = new QFile(fName);
  if (!cf->open(IO_WriteOnly))
  {
    err("Could not open file %s for writing\n",fName.data());
    exit(1);
  }
  /* Write the header of the contents file */
  cts.setDevice(cf);
  cts << "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML//EN\">\n"
         "<HTML><HEAD></HEAD><BODY>\n"
         "<OBJECT type=\"text/site properties\">\n"
         "<param name=\"ImageType\" value=\"Folder\">\n"
         "</OBJECT>\n"
         "<UL>\n";
  
  /* open the contents file */
  fName = Config::htmlOutputDir + "/index.hhk";
  kf = new QFile(fName);
  if (!kf->open(IO_WriteOnly))
  {
    err("Could not open file %s for writing\n",fName.data());
    exit(1);
  }
  /* Write the header of the contents file */
  kts.setDevice(kf);
  kts << "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML//EN\">\n"
         "<HTML><HEAD></HEAD><BODY>\n"
         "<OBJECT type=\"text/site properties\">\n"
         "<param name=\"ImageType\" value=\"Folder\">\n"
         "</OBJECT>\n"
         "<UL>\n";

  /* Write the project file */
  fName = Config::htmlOutputDir + "/index.hhp";
  QFile f(fName);
  if (f.open(IO_WriteOnly))
  {
    QTextStream t(&f);
    t << "[OPTIONS]\n"
         "Compatibility=1.1\n"
         "Full-text search=Yes\n"
         "Contents file=index.hhc\n"
         "Default Window=indexHelp\n"
         "Default topic=index.html\n"
         "Index file=index.hhk\n"
         "Title=" << Config::projectName << endl << endl 
      << "[FILES]\n"
         "index.html";
    f.close();
  }
  else
  {
    err("Could not open file %s for writing\n",fName.data());
  }
}

/*! Finalizes the HTML help. This will finish and close the
 *  contents file (index.hhc) and the index file (index.hhk).
 *  \sa initialize()
 */
void HtmlHelp::finalize()
{
  // end the contents file
  cts << "</UL>\n";
  cts.unsetDevice();
  cf->close();
  delete cf;
  
  index->writeFields(kts);
  
  // end the index file
  kts << "</UL>\n";
  kts.unsetDevice();
  kf->close();
  delete kf;
}

/*! Increase the level of the contents hierarchy. 
 *  This will start a new unnumbered HTML list in contents file.
 *  \sa decContentsDepth()
 */
int HtmlHelp::incContentsDepth()
{
  int i; for (i=0;i<dc+1;i++) cts << "  ";
  cts << "<UL>\n";
  return ++dc;
}

/*! Decrease the level of the contents hierarchy.
 *  This will end the unnumber HTML list.
 *  \sa incContentsDepth()
 */
int HtmlHelp::decContentsDepth()
{
  int i; for (i=0;i<dc;i++) cts << "  ";
  cts << "</UL>\n";
  return --dc;
}

/*! Add an list item to the contents file.
 *  \param name the name of the item.
 *  \param ref  the URL of to the item.
 */
void HtmlHelp::addContentsItem(const char *name,const char *ref, 
                               const char *anchor)
{
  int i; for (i=0;i<dc;i++) cts << "  ";
  cts << "<LI><OBJECT type=\"text/sitemap\">";
  if (ref)
  {
    cts << "<param name=\"Local\" value=\"" << ref << ".html";
    if (anchor) cts << "#" << anchor;  
    cts << "\">";
  }
  cts << "<param name=\"Name\" value=\"" << name << "\">"
         "</OBJECT>\n";
}

/*! Add an list item to the index file.
 *  \param name the name of the item.
 *  \param ref  the URL of to the item.
 *  \sa HtmlHelpIndex
 */
void HtmlHelp::addIndexItem(const char *level1, const char *level2,
                            const char *ref, const char *anchor)
{
  index->addItem(level1,level2,ref,anchor,TRUE);
  index->addItem(level2,level1,ref,anchor,FALSE);
}