/******************************************************************************
 *
 * 
 *
 *
 * Copyright (C) 1997-2010 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.
 *
 */

#include <stdlib.h>

#include "qtbc.h"
#include "defgen.h"
#include "doxygen.h"
#include "message.h"
#include "config.h"
#include "classlist.h"
#include "util.h"
#include "defargs.h"
#include "outputgen.h"
#include "dot.h"

#include <qdir.h>
#include <qfile.h>
#include <qtextstream.h>

#define DEF_DB(x)

inline void writeDEFString(QTextStream &t,const char *s)
{
  const char* p=s;
  char c;

  t << '\'';
  while ((c = *(p++)))
  {
    if (c == '\'')
      t << '\\';
    t << c;
  }
  t << '\'';
}

void generateDEFForMember(MemberDef *md,
    QTextStream &t,
    Definition *def,
    const char* Prefix)
{
  QCString memPrefix;

  // + declaration
  // - reimplements
  // - reimplementedBy
  // - exceptions
  // - const/volatile specifiers
  // - examples
  // + source definition
  // - source references
  // - source referenced by
  // - include code 

  if (md->memberType()==MemberDef::EnumValue) return;

  QCString scopeName;
  if (md->getClassDef()) 
    scopeName=md->getClassDef()->name();
  else if (md->getNamespaceDef()) 
    scopeName=md->getNamespaceDef()->name();

  t << "    " << Prefix << "-member = {" << endl;
  memPrefix = "      ";
  memPrefix.append( Prefix );
  memPrefix.append( "-mem-" );

  QCString memType;
  bool isFunc=FALSE;
  switch (md->memberType())
  {
    case MemberDef::Define:      memType="define";    break;
    case MemberDef::EnumValue:   ASSERT(0);           break;
    case MemberDef::Property:    memType="property";  break;
    case MemberDef::Event:       memType="event";     break;
    case MemberDef::Variable:    memType="variable";  break;
    case MemberDef::Typedef:     memType="typedef";   break;
    case MemberDef::Enumeration: memType="enum";      break;
    case MemberDef::Function:    memType="function";  isFunc=TRUE; break;
    case MemberDef::Signal:      memType="signal";    isFunc=TRUE; break;
    case MemberDef::Friend:      memType="friend";    isFunc=TRUE; break;
    case MemberDef::DCOP:        memType="dcop";      isFunc=TRUE; break;
    case MemberDef::Slot:        memType="slot";      isFunc=TRUE; break;
  }

  t << memPrefix << "kind = '" << memType << "';" << endl;
  t << memPrefix << "id   = '"
    << md->getOutputFileBase() << "_1" << md->anchor()
    << "';" << endl;

  t << memPrefix << "virt = ";
  switch (md->virtualness())
  {
    case Normal:  t << "normal;"       << endl; break;
    case Virtual: t << "virtual;"      << endl; break;
    case Pure:    t << "pure-virtual;" << endl; break;
    default: ASSERT(0);
  }

  t << memPrefix << "prot = ";
  switch(md->protection())
  {
    case Public:    t << "public;"    << endl; break;
    case Protected: t << "protected;" << endl; break;
    case Private:   t << "private;"   << endl; break;
    case Package:   t << "package;"   << endl; break;
  }

  if (md->memberType()!=MemberDef::Define &&
      md->memberType()!=MemberDef::Enumeration
     )
  {
    QCString typeStr = replaceAnonymousScopes(md->typeString());
    t << memPrefix << "type = <<_EnD_oF_dEf_TeXt_" << endl << typeStr << endl
      << "_EnD_oF_dEf_TeXt_;" << endl;
  }

  t << memPrefix << "name = '" << md->name() << "';" << endl;

  if (isFunc) //function
  {
    ArgumentList *declAl = new ArgumentList;
    LockingPtr<ArgumentList> defAl = md->argumentList();
    stringToArgumentList(md->argsString(),declAl);
    QCString fcnPrefix = "  " + memPrefix + "param-";

    if (declAl->count()>0)
    {
      ArgumentListIterator declAli(*declAl);
      ArgumentListIterator defAli(*defAl);
      Argument *a;
      for (declAli.toFirst();(a=declAli.current());++declAli)
      {
        Argument *defArg = defAli.current();
        t << memPrefix << "param = {" << endl;
        if (!a->attrib.isEmpty())
        {
          t << fcnPrefix << "attributes = ";
          writeDEFString(t,a->attrib);
          t << ';' << endl;
        }
        if (!a->type.isEmpty())
        {
          t << fcnPrefix << "type = <<_EnD_oF_dEf_TeXt_" << endl
            << a->type << endl << "_EnD_oF_dEf_TeXt_;" << endl;
        }
        if (!a->name.isEmpty())
        {
          t << fcnPrefix << "declname = ";
          writeDEFString(t,a->name);
          t << ';' << endl;
        }
        if (defArg && !defArg->name.isEmpty() && defArg->name!=a->name)
        {
          t << fcnPrefix << "defname = ";
          writeDEFString(t,defArg->name);
          t << ';' << endl;
        }
        if (!a->array.isEmpty())
        {
          t << fcnPrefix << "array = ";
          writeDEFString(t,a->array); 
          t << ';' << endl;
        }
        if (!a->defval.isEmpty())
        {
          t << fcnPrefix << "defval = <<_EnD_oF_dEf_TeXt_" << endl
            << a->defval << endl << "_EnD_oF_dEf_TeXt_;" << endl;
        }
        if (defArg) ++defAli;
        t << "      }; /*" << fcnPrefix << "-param */" << endl;
      }
    }
    delete declAl;
  }
  else if (  md->memberType()==MemberDef::Define
      && md->argsString()!=0)
  {
    ArgumentListIterator ali(*md->argumentList());
    Argument *a;
    QCString defPrefix = "  " + memPrefix + "def-";

    for (ali.toFirst();(a=ali.current());++ali)
    {
      t << memPrefix << "param  = {" << endl;
      t << defPrefix << "name = '" << a->type << "';" << endl;
      t << "      }; /*" << defPrefix << "-param */" << endl;
    }
  }

  if (!md->initializer().isEmpty())
  {
    t << memPrefix << "initializer = <<_EnD_oF_dEf_TeXt_" << endl
      << md->initializer() << endl << "_EnD_oF_dEf_TeXt_;" << endl;
  }
  // TODO: exceptions, const volatile
  if (md->memberType()==MemberDef::Enumeration) // enum
  {
    LockingPtr<MemberList> enumList = md->enumFieldList();
    if (enumList!=0)
    {
      MemberListIterator emli(*enumList);
      MemberDef *emd;
      for (emli.toFirst();(emd=emli.current());++emli)
      {
        t << memPrefix << "enum = { enum-name = " << emd->name() << ';';
        if (!emd->initializer().isEmpty())
        {
          t << " enum-value = ";
          writeDEFString(t,emd->initializer());
          t << ';';
        }
        t << " };" << endl;
      }
    }
  }

  t << memPrefix << "desc-file = '" << md->getDefFileName() << "';" << endl;
  t << memPrefix << "desc-line = '" << md->getDefLine()     << "';" << endl;
  t << memPrefix << "briefdesc =    <<_EnD_oF_dEf_TeXt_"    << endl
    << md->briefDescription() << endl << "_EnD_oF_dEf_TeXt_;" << endl;
  t << memPrefix << "documentation = <<_EnD_oF_dEf_TeXt_"   << endl
    << md->documentation() << endl << "_EnD_oF_dEf_TeXt_;" << endl;

  //printf("md->getReferencesMembers()=%p\n",md->getReferencesMembers());

  LockingPtr<MemberSDict> mdict = md->getReferencesMembers();
  if (!mdict.isNull())
  {
    MemberSDict::Iterator mdi(*mdict);
    MemberDef *rmd;
    QCString refPrefix = "  " + memPrefix + "ref-";

    for (mdi.toFirst();(rmd=mdi.current());++mdi)
    {
      if (rmd->getStartBodyLine()!=-1 && rmd->getBodyDef())
      {
        t << memPrefix << "referenceto = {" << endl;
        t << refPrefix << "id = '"
          << rmd->getBodyDef()->getOutputFileBase()
          << "_1"   // encoded `:' character (see util.cpp:convertNameToFile)
          << rmd->anchor() << "';" << endl;

        t << refPrefix << "line = '"
          << rmd->getStartBodyLine() << "';" << endl;

        QCString scope = rmd->getScopeString();
        QCString name = rmd->name();
        if (!scope.isEmpty() && scope!=def->name())
        {
          name.prepend(scope+"::");
        }

        t << refPrefix << "name = ";
        writeDEFString(t,name);
        t << ';' << endl << "    };" << endl;
      }
    } /* for (mdi.toFirst...) */
  }
  mdict = md->getReferencedByMembers();
  if (!mdict.isNull())
  {
    MemberSDict::Iterator mdi(*mdict);
    MemberDef *rmd;
    QCString refPrefix = "  " + memPrefix + "ref-";

    for (mdi.toFirst();(rmd=mdi.current());++mdi)
    {
      if (rmd->getStartBodyLine()!=-1 && rmd->getBodyDef())
      {
        t << memPrefix << "referenceby = {" << endl;
        t << refPrefix << "id = '"
          << rmd->getBodyDef()->getOutputFileBase()
          << "_1"   // encoded `:' character (see util.cpp:convertNameToFile)
          << rmd->anchor() << "';" << endl;

        t << refPrefix << "line = '"
          << rmd->getStartBodyLine() << "';" << endl;

        QCString scope = rmd->getScopeString();
        QCString name = rmd->name();
        if (!scope.isEmpty() && scope!=def->name())
        {
          name.prepend(scope+"::");
        }

        t << refPrefix << "name = ";
        writeDEFString(t,name);
        t << ';' << endl << "    };" << endl;
      }
    } /* for (mdi.toFirst...) */
  }

  t << "    }; /* " << Prefix << "-member */" << endl;
}


void generateDEFClassSection(ClassDef *cd,
    QTextStream &t,
    MemberList *ml,
    const char *kind)
{
  if (cd && ml && ml->count()>0)
  {
    t << "  cp-section = {" << endl;
    t << "    sec-kind = '" << kind << "';" << endl;

    MemberListIterator mli(*ml);
    MemberDef *md;
    for (mli.toFirst();(md=mli.current());++mli)
    {
      generateDEFForMember(md,t,cd,"sec");
    }
    t << "  }; /* cp-section */" << endl;
  }
}

void generateDEFForClass(ClassDef *cd,QTextStream &t)
{
  // + brief description
  // + detailed description
  // - template arguments
  // - include files
  // + inheritance diagram
  // + list of direct super classes
  // + list of direct sub classes
  // + collaboration diagram
  // - list of all members
  // + user defined member sections
  // + standard member sections
  // + detailed member documentation
  // - examples

  if (cd->isReference()) return; // skip external references.
  if (cd->name().find('@')!=-1) return; // skip anonymous compounds.
  if (cd->templateMaster()!=0) return; // skip generated template instances.

  t << cd->compoundTypeString() << " = {" << endl;
  t << "  cp-id     = '" << cd->getOutputFileBase() << "';" << endl;
  t << "  cp-name   = '" << cd->name() << "';" << endl;

  if (cd->baseClasses())
  {
    BaseClassListIterator bcli(*cd->baseClasses());
    BaseClassDef *bcd;
    for (bcli.toFirst();(bcd=bcli.current());++bcli)
    {
      t << "  cp-ref     = {" << endl << "    ref-type = base;" << endl;
      t << "    ref-id   = '"
        << bcd->classDef->getOutputFileBase() << "';" << endl;
      t << "    ref-prot = ";
      switch (bcd->prot)
      {
        case Public:    t << "public;"    << endl; break;
        case Package: // package scope is not possible
        case Protected: t << "protected;" << endl; break;
        case Private:   t << "private;"   << endl; break;
      }
      t << "    ref-virt = ";
      switch(bcd->virt)
      {
        case Normal:  t << "non-virtual;";  break;
        case Virtual: t << "virtual;";      break;
        case Pure:    t << "pure-virtual;"; break;
      }
      t << endl << "  };" << endl;
    }
  }

  if (cd->subClasses())
  {
    BaseClassListIterator bcli(*cd->subClasses());
    BaseClassDef *bcd;
    for (bcli.toFirst();(bcd=bcli.current());++bcli)
    {
      t << "  cp-ref     = {" << endl << "    ref-type = derived;" << endl;
      t << "    ref-id   = '"
        << bcd->classDef->getOutputFileBase() << "';" << endl;
      t << "    ref-prot = ";
      switch (bcd->prot)
      {
        case Public:    t << "public;"    << endl; break;
        case Package: // packet scope is not possible!
        case Protected: t << "protected;" << endl; break;
        case Private:   t << "private;"   << endl; break;
      }
      t << "    ref-virt = ";
      switch(bcd->virt)
      {
        case Normal:  t << "non-virtual;";  break;
        case Virtual: t << "virtual;";      break;
        case Pure:    t << "pure-virtual;"; break;
      }
      t << endl << "  };" << endl;
    }
  }

  int numMembers = 0;
  QListIterator<MemberList> mli(cd->getMemberLists());
  MemberList *ml;
  for (mli.toFirst();(ml=mli.current());++mli)
  {
    if ((ml->listType()&MemberList::detailedLists)==0)
    {
      numMembers+=ml->count();
    }
  }
  if (numMembers>0)
  {
    generateDEFClassSection(cd,t,cd->getMemberList(MemberList::pubTypes),"public-type");
    generateDEFClassSection(cd,t,cd->getMemberList(MemberList::pubMethods),"public-func");
    generateDEFClassSection(cd,t,cd->getMemberList(MemberList::pubAttribs),"public-attrib");
    generateDEFClassSection(cd,t,cd->getMemberList(MemberList::pubSlots),"public-slot");
    generateDEFClassSection(cd,t,cd->getMemberList(MemberList::signals),"signal");
    generateDEFClassSection(cd,t,cd->getMemberList(MemberList::dcopMethods),"dcop-func");
    generateDEFClassSection(cd,t,cd->getMemberList(MemberList::properties),"property");
    generateDEFClassSection(cd,t,cd->getMemberList(MemberList::pubStaticMethods),"public-static-func");
    generateDEFClassSection(cd,t,cd->getMemberList(MemberList::pubStaticAttribs),"public-static-attrib");
    generateDEFClassSection(cd,t,cd->getMemberList(MemberList::proTypes),"protected-type");
    generateDEFClassSection(cd,t,cd->getMemberList(MemberList::proMethods),"protected-func");
    generateDEFClassSection(cd,t,cd->getMemberList(MemberList::proAttribs),"protected-attrib");
    generateDEFClassSection(cd,t,cd->getMemberList(MemberList::proSlots),"protected-slot");
    generateDEFClassSection(cd,t,cd->getMemberList(MemberList::proStaticMethods),"protected-static-func");
    generateDEFClassSection(cd,t,cd->getMemberList(MemberList::proStaticAttribs),"protected-static-attrib");
    generateDEFClassSection(cd,t,cd->getMemberList(MemberList::priTypes),"private-type");
    generateDEFClassSection(cd,t,cd->getMemberList(MemberList::priMethods),"private-func");
    generateDEFClassSection(cd,t,cd->getMemberList(MemberList::priAttribs),"private-attrib");
    generateDEFClassSection(cd,t,cd->getMemberList(MemberList::priSlots),"private-slot");
    generateDEFClassSection(cd,t,cd->getMemberList(MemberList::priStaticMethods),"private-static-func");
    generateDEFClassSection(cd,t,cd->getMemberList(MemberList::priStaticAttribs),"private-static-attrib");
    generateDEFClassSection(cd,t,cd->getMemberList(MemberList::friends),"signal");
    generateDEFClassSection(cd,t,cd->getMemberList(MemberList::related),"related");
  }

  t << "  cp-filename  = '" << cd->getDefFileName() << "';" << endl;
  t << "  cp-fileline  = '" << cd->getDefLine()     << "';" << endl;
  t << "  cp-briefdesc = <<_EnD_oF_dEf_TeXt_" << endl
    << cd->briefDescription() << endl << "_EnD_oF_dEf_TeXt_;" << endl;

  t << "  cp-documentation = <<_EnD_oF_dEf_TeXt_" << endl
    << cd->documentation() << endl << "_EnD_oF_dEf_TeXt_;" << endl;

  DotClassGraph inheritanceGraph(cd,DotNode::Inheritance);
  if (!inheritanceGraph.isTrivial())
  {
    t << "  cp-inheritancegraph = <<_EnD_oF_dEf_TeXt_" << endl;
    inheritanceGraph.writeDEF(t);
    t << endl << "_EnD_oF_dEf_TeXt_;" << endl;
  }
  DotClassGraph collaborationGraph(cd,DotNode::Collaboration);
  if (!collaborationGraph.isTrivial())
  {
    t << "  cp-collaborationgraph = <<_EnD_oF_dEf_TeXt_" << endl;
    collaborationGraph.writeDEF(t);
    t << endl << "_EnD_oF_dEf_TeXt_;" << endl;
  }
  t << "}; /* " <<  cd->compoundTypeString() << " */" << endl;
}

void generateDEFSection(Definition *d,
    QTextStream &t,
    MemberList *ml,
    const char *kind)
{
  if (ml && ml->count()>0)
  {
    t << "    " << kind << " = {" << endl;
    MemberListIterator mli(*ml);
    MemberDef *md;
    for (mli.toFirst();(md=mli.current());++mli)
    {
      generateDEFForMember(md,t,d,kind);
    }
    t << "    };" << endl;
  }
}

void generateDEFForNamespace(NamespaceDef *nd,QTextStream &t)
{
  if (nd->isReference()) return; // skip external references
  t << "  namespace = {" << endl;
  t << "    ns-id   = '" << nd->getOutputFileBase() << "';" << endl;
  t << "    ns-name = ";
  writeDEFString(t,nd->name());
  t << ';' << endl;

  generateDEFSection(nd,t,nd->getMemberList(MemberList::decDefineMembers),"define");
  generateDEFSection(nd,t,nd->getMemberList(MemberList::decProtoMembers),"prototype");
  generateDEFSection(nd,t,nd->getMemberList(MemberList::decTypedefMembers),"typedef");
  generateDEFSection(nd,t,nd->getMemberList(MemberList::decEnumMembers),"enum");
  generateDEFSection(nd,t,nd->getMemberList(MemberList::decFuncMembers),"func");
  generateDEFSection(nd,t,nd->getMemberList(MemberList::decVarMembers),"var");

  t << "  ns-filename  = '" << nd->getDefFileName() << "';" << endl;
  t << "  ns-fileline  = '" << nd->getDefLine()     << "';" << endl;
  t << "  ns-briefdesc = <<_EnD_oF_dEf_TeXt_" << endl
    << nd->briefDescription() << endl << "_EnD_oF_dEf_TeXt_;" << endl;

  t << "  ns-documentation = <<_EnD_oF_dEf_TeXt_" << endl
    << nd->documentation() << endl << "_EnD_oF_dEf_TeXt_;" << endl;
  t << "  };" << endl;
}

void generateDEFForFile(FileDef *fd,QTextStream &t)
{
  if (fd->isReference()) return; // skip external references

  t << "file = {" << endl;
  t << "  file-id   = '" << fd->getOutputFileBase() << "';" << endl;
  t << "  file-name = ";
  writeDEFString(t,fd->name());
  t << ';' << endl;

  generateDEFSection(fd,t,fd->getMemberList(MemberList::decDefineMembers),"define");
  generateDEFSection(fd,t,fd->getMemberList(MemberList::decProtoMembers),"prototype");
  generateDEFSection(fd,t,fd->getMemberList(MemberList::decTypedefMembers),"typedef");
  generateDEFSection(fd,t,fd->getMemberList(MemberList::decEnumMembers),"enum");
  generateDEFSection(fd,t,fd->getMemberList(MemberList::decFuncMembers),"func");
  generateDEFSection(fd,t,fd->getMemberList(MemberList::decVarMembers),"var");

  t << "  file-full-name  = '" << fd->getDefFileName() << "';" << endl;
  t << "  file-first-line = '" << fd->getDefLine()     << "';" << endl;

  t << "  file-briefdesc  = <<_EnD_oF_dEf_TeXt_" << endl
    << fd->briefDescription() << endl << "_EnD_oF_dEf_TeXt_;" << endl;

  t << "  file-documentation = <<_EnD_oF_dEf_TeXt_" << endl
    << fd->documentation() << endl << "_EnD_oF_dEf_TeXt_;" << endl;

  t << "}; /* file */" << endl;
}


void generateDEF()
{
  QCString outputDirectory = Config_getString("OUTPUT_DIRECTORY");
  if (outputDirectory.isEmpty())
  {
    outputDirectory=QDir::currentDirPath();
  }
  else
  {
    QDir dir(outputDirectory);
    if (!dir.exists())
    {
      dir.setPath(QDir::currentDirPath());
      if (!dir.mkdir(outputDirectory))
      {
        err("Error: tag OUTPUT_DIRECTORY: Output directory `%s' does not "
            "exist and cannot be created\n",outputDirectory.data());
        exit(1);
      }
      else if (!Config_getBool("QUIET"))
      {
        err("Notice: Output directory `%s' does not exist. "
            "I have created it for you.\n", outputDirectory.data());
      }
      dir.cd(outputDirectory);
    }
    outputDirectory=dir.absPath();
  }

  QDir dir(outputDirectory);
  if (!dir.exists())
  {
    dir.setPath(QDir::currentDirPath());
    if (!dir.mkdir(outputDirectory))
    {
      err("Cannot create directory %s\n",outputDirectory.data());
      return;
    }
  }
  QDir defDir(outputDirectory+"/def");
  if (!defDir.exists() && !defDir.mkdir(outputDirectory+"/def"))
  {
    err("Could not create def directory in %s\n",outputDirectory.data());
    return;
  }

  QCString fileName=outputDirectory+"/def/doxygen.def";
  QFile f(fileName);
  if (!f.open(IO_WriteOnly))
  {
    err("Cannot open file %s for writing!\n",fileName.data());
    return;
  }
  QTextStream t(&f);
  t << "AutoGen Definitions dummy;" << endl;

  if (Doxygen::classSDict->count()+Doxygen::inputNameList->count()>0)
  {
    ClassSDict::Iterator cli(*Doxygen::classSDict);
    ClassDef *cd;
    for (cli.toFirst();(cd=cli.current());++cli)
    {
      generateDEFForClass(cd,t);
    }
    FileNameListIterator fnli(*Doxygen::inputNameList);
    FileName *fn;
    for (;(fn=fnli.current());++fnli)
    {
      FileNameIterator fni(*fn);
      FileDef *fd;
      for (;(fd=fni.current());++fni)
      {
        generateDEFForFile(fd,t);
      }
    }
  }
  else
  {
    t << "dummy_value = true;" << endl;
  }
}
