/*=========================================================================

  Program:   CMake - Cross-Platform Makefile Generator
  Module:    $RCSfile$
  Language:  C++
  Date:      $Date$
  Version:   $Revision$

  Copyright (c) 2002 Kitware, Inc., Insight Consortium.  All rights reserved.
  See Copyright.txt or http://www.cmake.org/HTML/Copyright.html for details.

     This software is distributed WITHOUT ANY WARRANTY; without even 
     the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
     PURPOSE.  See the above copyright notices for more information.

=========================================================================*/
#include "cmDocumentation.h"

#include "cmSystemTools.h"

//----------------------------------------------------------------------------
static const cmDocumentationEntry cmDocumentationStandardOptions[] =
{
  {"--copyright", "Print the CMake copyright and exit.", 0},
  {"--help", "Print usage information and exit.",
   "Usage describes the basic command line interface and its options."},
  {"--help-full", "Print full help and exit.",
   "Full help displays most of the documentation provided by the UNIX "
   "man page.  It is provided for use on non-UNIX platforms, but is "
   "also convenient if the man page is not installed."},
  {"--help-html", "Print full help in HTML format.",
   "This option is used by CMake authors to help produce web pages."},
  {"--help-man", "Print a UNIX man page and exit.",
   "This option is used by CMake authors to generate the UNIX man page."},
  {"--version", "Show program name/version banner and exit.", 0},
  {0,0,0}
};

//----------------------------------------------------------------------------
static const cmDocumentationEntry cmDocumentationCommandsHeader[] =
{
  {0,
   "The following commands are available in CMakeLists.txt code:", 0},
  {0,0,0}
};

//----------------------------------------------------------------------------
const cmDocumentationEntry cmDocumentationMailingList[] =
{
  {0,
   "For help and discussion about using cmake, a mailing list is provided "
   "at cmake@www.cmake.org.  Please first read the full documentation at "
   "http://www.cmake.org before posting questions to the list.", 0},
  {0,0,0}
};

//----------------------------------------------------------------------------
const cmDocumentationEntry cmDocumentationAuthor[] =
{
  {0,
   "This manual page was generated by \"cmake --help-man\".", 0},
  {0,0,0}
};

//----------------------------------------------------------------------------
const cmDocumentationEntry cmDocumentationCopyright[] =
{
  {0,
   "Copyright (c) 2002 Kitware, Inc., Insight Consortium.  "
   "All rights reserved.", 0},
  {0,
   "Redistribution and use in source and binary forms, with or without "
   "modification, are permitted provided that the following conditions are "
   "met:", 0},
  {"",
   "Redistributions of source code must retain the above copyright notice, "
   "this list of conditions and the following disclaimer.", 0},
  {"",
   "Redistributions in binary form must reproduce the above copyright "
   "notice, this list of conditions and the following disclaimer in the "
   "documentation and/or other materials provided with the distribution.",
   0},
  {"",
   "The names of Kitware, Inc., the Insight Consortium, or the names of "
   "any consortium members, or of any contributors, may not be used to "
   "endorse or promote products derived from this software without "
   "specific prior written permission.", 0},
  {"",
   "Modified source versions must be plainly marked as such, and must "
   "not be misrepresented as being the original software.", 0},
  {0,
   "THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "
   "``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT "
   "LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR "
   "A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR "
   "CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, "
   "EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, "
   "PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR "
   "PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF "
   "LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING "
   "NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS "
   "SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.", 0},
  {0, 0, 0}
};

//----------------------------------------------------------------------------
cmDocumentation::cmDocumentation()
{
  this->CurrentForm = TextForm;
  this->TextIndent = "";
  this->TextWidth = 77;
}

//----------------------------------------------------------------------------
void cmDocumentation::PrintCopyright(std::ostream& os)
{
  os << "CMake version " CMake_VERSION_STRING "\n";
  for(const cmDocumentationEntry* op = cmDocumentationCopyright;
      op->brief; ++op)
    {
    if(op->name)
      {
      os << " * ";
      this->TextIndent = "    ";
      this->PrintColumn(os, op->brief);
      }
    else
      {
      this->TextIndent = "";
      this->PrintColumn(os, op->brief);
      }
    os << "\n";
    }
}

//----------------------------------------------------------------------------
void cmDocumentation::PrintVersion(std::ostream& os)
{
  os << "CMake version " CMake_VERSION_STRING "\n";
}

//----------------------------------------------------------------------------
void cmDocumentation::AddSection(const char* name,
                                 const cmDocumentationEntry* d)
{
  this->Names.push_back(name);
  this->Sections.push_back(d);
}

//----------------------------------------------------------------------------
void cmDocumentation::ClearSections()
{
  this->Names.erase(this->Names.begin(), this->Names.end());
  this->Sections.erase(this->Sections.begin(), this->Sections.end());
}

//----------------------------------------------------------------------------
void cmDocumentation::PrintDocumentation(Type ht, std::ostream& os)
{
  switch (ht)
    {
    case cmDocumentation::Usage:     this->PrintDocumentationUsage(os); break;
    case cmDocumentation::Full:      this->PrintDocumentationFull(os); break;
    case cmDocumentation::HTML:      this->PrintDocumentationHTML(os); break;
    case cmDocumentation::Man:       this->PrintDocumentationMan(os); break;
    case cmDocumentation::Copyright: this->PrintCopyright(os); break;
    case cmDocumentation::Version:   this->PrintVersion(os); break;
    default: break;
    }
}

//----------------------------------------------------------------------------
cmDocumentation::Type cmDocumentation::CheckOptions(int argc, char** argv)
{
  // Providing zero arguments gives usage information.
  if(argc == 1)
    {
    return cmDocumentation::Usage;
    }
  
  // Search for supported help options.
  for(int i=1; i < argc; ++i)
    {
    if((strcmp(argv[i], "-help") == 0) ||
       (strcmp(argv[i], "--help") == 0) ||
       (strcmp(argv[i], "/?") == 0) ||
       (strcmp(argv[i], "-usage") == 0) ||
       (strcmp(argv[i], "-h") == 0) ||
       (strcmp(argv[i], "-H") == 0))
      {
      return cmDocumentation::Usage;
      }
    if(strcmp(argv[i], "--help-full") == 0)
      {
      return cmDocumentation::Full;
      }
    if(strcmp(argv[i], "--help-html") == 0)
      {
      return cmDocumentation::HTML;
      }
    if(strcmp(argv[i], "--help-man") == 0)
      {
      return cmDocumentation::Man;
      }
    if(strcmp(argv[i], "--copyright") == 0)
      {
      return cmDocumentation::Copyright;
      }
    if((strcmp(argv[i], "--version") == 0) || 
       (strcmp(argv[i], "-version") == 0) || 
       (strcmp(argv[i], "-V") == 0) || 
       (strcmp(argv[i], "/V") == 0))
      {
      return cmDocumentation::Version;
      }
    }
  return cmDocumentation::None;
}

//----------------------------------------------------------------------------
void cmDocumentation::Print(Form f, std::ostream& os)
{
  this->CurrentForm = f;
  for(unsigned int i=0; i < this->Sections.size(); ++i)
    {
    this->PrintSection(os, this->Sections[i], this->Names[i]);
    }
}

//----------------------------------------------------------------------------
void cmDocumentation::SetNameSection(const cmDocumentationEntry* section)
{
  this->SetSection(0, section, 0, this->NameSection);
}

//----------------------------------------------------------------------------
void cmDocumentation::SetUsageSection(const cmDocumentationEntry* section)
{
  this->SetSection(0, section, 0, this->UsageSection);
}

//----------------------------------------------------------------------------
void cmDocumentation::SetDescriptionSection(const cmDocumentationEntry* section)
{
  this->SetSection(0, section, 0, this->DescriptionSection);
}

//----------------------------------------------------------------------------
void cmDocumentation::SetOptionsSection(const cmDocumentationEntry* section)
{
  this->SetSection(0, section, cmDocumentationStandardOptions,
                   this->OptionsSection);
}

//----------------------------------------------------------------------------
void cmDocumentation::SetCommandsSection(const cmDocumentationEntry* section)
{
  this->SetSection(cmDocumentationCommandsHeader, section, 0,
                   this->CommandsSection);
}

//----------------------------------------------------------------------------
void cmDocumentation::PrintSection(std::ostream& os,
                                   const cmDocumentationEntry* section,
                                   const char* name)
{
  switch (this->CurrentForm)
    {
    case TextForm: this->PrintSectionText(os, section, name); break;
    case HTMLForm: this->PrintSectionHTML(os, section, name); break;
    case ManForm: this->PrintSectionMan(os, section, name); break;
    case UsageForm: this->PrintSectionUsage(os, section, name); break;
    }
}

//----------------------------------------------------------------------------
void cmDocumentation::PrintSectionText(std::ostream& os,
                                       const cmDocumentationEntry* section,
                                       const char* name)
{
  if(name)
    {
    os << name << "\n\n";
    }
  if(!section) { return; }
  for(const cmDocumentationEntry* op = section; op->brief; ++op)
    {
    if(op->name)
      {
      if(op->name[0])
        {
        os << "  " << op->name << "\n";
        }
      this->TextIndent = "       ";
      this->PrintFormatted(os, op->brief);
      if(op->full)
        {
        os << "\n";
        this->PrintFormatted(os, op->full);
        }
      }
    else
      {
      this->TextIndent = "";
      this->PrintFormatted(os, op->brief);
      }
    os << "\n";
    }  
}

//----------------------------------------------------------------------------
void cmDocumentation::PrintSectionHTML(std::ostream& os,
                                       const cmDocumentationEntry* section,
                                       const char* name)
{
  if(name)
    {
    os << "<h2>" << name << "</h2>\n";
    }
  if(!section) { return; }
  for(const cmDocumentationEntry* op = section; op->brief;)
    {
    if(op->name)
      {
      os << "<ul>\n";
      for(;op->name;++op)
        {
        os << "  <li>\n";
        if(op->name[0])
          {
          os << "    <b><code>";
          this->PrintHTMLEscapes(os, op->name);
          os << "</code></b>: ";
          }
        this->PrintHTMLEscapes(os, op->brief);
        if(op->full)
          {
          os << "<br>\n    ";
          this->PrintFormatted(os, op->full);
          }
        os << "\n";
        os << "  </li>\n";
        }
      os << "</ul>\n";
      }
    else
      {
      this->PrintFormatted(os, op->brief);
      os << "\n";
      ++op;
      }
    }
}

//----------------------------------------------------------------------------
void cmDocumentation::PrintSectionMan(std::ostream& os,
                                      const cmDocumentationEntry* section,
                                      const char* name)
{
  if(name)
    {
    os << ".SH " << name << "\n";
    }
  if(!section) { return; }
  for(const cmDocumentationEntry* op = section; op->brief; ++op)
    {
    if(op->name)
      {
      os << ".TP\n"
         << ".B " << (op->name[0]?op->name:"*") << "\n";
      this->PrintFormatted(os, op->brief);
      this->PrintFormatted(os, op->full);
      }
    else
      {
      os << ".PP\n";
      this->PrintFormatted(os, op->brief);
      }
    }  
}

//----------------------------------------------------------------------------
void cmDocumentation::PrintSectionUsage(std::ostream& os,
                                        const cmDocumentationEntry* section,
                                        const char* name)
{
  if(name)
    {
    os << name << "\n";
    }
  if(!section) { return; }
  for(const cmDocumentationEntry* op = section; op->brief; ++op)
    {
    if(op->name)
      {
      os << "  " << op->name;
      this->TextIndent = "                             ";
      int align = static_cast<int>(strlen(this->TextIndent))-4;
      for(int i = static_cast<int>(strlen(op->name)); i < align; ++i)
        {
        os << " ";
        }
      os << "= ";
      this->PrintColumn(os, op->brief);
      os << "\n";
      }
    else
      {
      os << "\n";
      this->TextIndent = "";
      this->PrintFormatted(os, op->brief);
      }
    }
  os << "\n";
}

//----------------------------------------------------------------------------
void cmDocumentation::PrintFormatted(std::ostream& os, const char* text)
{
  if(!text)
    {
    return;
    }
  const char* ptr = text;
  while(*ptr)
    {
    // Any ptrs starting in a space are treated as preformatted text.
    std::string preformatted;
    while(*ptr == ' ')
      {
      for(char ch = *ptr; ch && ch != '\n'; ++ptr, ch = *ptr)
        {
        preformatted.append(1, ch);
        }
      if(*ptr)
        {
        ++ptr;
        preformatted.append(1, '\n');
        }
      }
    if(preformatted.length())
      {
      this->PrintPreformatted(os, preformatted.c_str());
      }
    
    // Other ptrs are treated as paragraphs.
    std::string paragraph;
    for(char ch = *ptr; ch && ch != '\n'; ++ptr, ch = *ptr)
      {
      paragraph.append(1, ch);
      }
    if(*ptr)
      {
      ++ptr;
      paragraph.append(1, '\n');
      }
    if(paragraph.length())
      {
      this->PrintParagraph(os, paragraph.c_str());
      }
    }
}

//----------------------------------------------------------------------------
void cmDocumentation::PrintPreformatted(std::ostream& os, const char* text)
{
  switch (this->CurrentForm)
    {
    case TextForm: this->PrintPreformattedText(os, text); break;
    case HTMLForm: this->PrintPreformattedHTML(os, text); break;
    case ManForm: this->PrintPreformattedMan(os, text); break;
    case UsageForm: this->PrintPreformattedText(os, text); break;
    }
}

//----------------------------------------------------------------------------
void cmDocumentation::PrintParagraph(std::ostream& os, const char* text)
{
  switch (this->CurrentForm)
    {
    case TextForm: this->PrintParagraphText(os, text); break;
    case HTMLForm: this->PrintParagraphHTML(os, text); break;
    case ManForm: this->PrintParagraphMan(os, text); break;
    case UsageForm: this->PrintParagraphText(os, text); break;
    }
}

//----------------------------------------------------------------------------
void cmDocumentation::PrintPreformattedText(std::ostream& os, const char* text)
{
  bool newline = true;
  for(const char* ptr = text; *ptr; ++ptr)
    {
    if(newline)
      {
      os << this->TextIndent;
      newline = false;
      }
    os << *ptr;
    if(*ptr == '\n')
      {
      newline = true;
      }
    }
  os << "\n";
}

//----------------------------------------------------------------------------
void cmDocumentation::PrintParagraphText(std::ostream& os, const char* text)
{
  os << this->TextIndent;
  this->PrintColumn(os, text);
  os << "\n";
}

//----------------------------------------------------------------------------
void cmDocumentation::PrintPreformattedHTML(std::ostream& os, const char* text)
{
  os << "<pre>";
  this->PrintHTMLEscapes(os, text);
  os << "</pre>\n    ";
}

//----------------------------------------------------------------------------
void cmDocumentation::PrintParagraphHTML(std::ostream& os, const char* text)
{
  os << "<p>";
  this->PrintHTMLEscapes(os, text);
}

//----------------------------------------------------------------------------
void cmDocumentation::PrintPreformattedMan(std::ostream& os, const char* text)
{
  os << text << "\n";
}

//----------------------------------------------------------------------------
void cmDocumentation::PrintParagraphMan(std::ostream& os, const char* text)
{
  os << text << "\n\n";
}

//----------------------------------------------------------------------------
void cmDocumentation::PrintColumn(std::ostream& os, const char* text)
{
  // Print text arranged in an indented column of fixed witdh.
  const char* l = text;
  int column = 0;
  bool newSentence = false;
  bool firstLine = true;
  int width = this->TextWidth - static_cast<int>(strlen(this->TextIndent));
  
  // Loop until the end of the text.
  while(*l)
    {
    // Parse the next word.
    const char* r = l;
    while(*r && (*r != '\n') && (*r != ' ')) { ++r; }
    
    // Does it fit on this line?
    if(r-l < (width-column-(newSentence?1:0)))
      {
      // Word fits on this line.
      if(r > l)
        {
        if(column)
          {
          // Not first word on line.  Separate from the previous word
          // by a space, or two if this is a new sentence.
          if(newSentence)
            {
            os << "  ";
            column += 2;
            }
          else
            {
            os << " ";
            column += 1;
            }
          }
        else
          {
          // First word on line.  Print indentation unless this is the
          // first line.
          os << (firstLine?"":this->TextIndent);
          }
        
        // Print the word.
        os.write(l, static_cast<long>(r-l));
        newSentence = (*(r-1) == '.');
        }
      
      if(*r == '\n')
        {
        // Text provided a newline.  Start a new line.
        os << "\n";
        ++r;
        column = 0;
        firstLine = false;
        }
      else
        {
        // No provided newline.  Continue this line.
        column += static_cast<long>(r-l);
        }
      }
    else
      {
      // Word does not fit on this line.  Start a new line.
      os << "\n";
      firstLine = false;
      if(r > l)
        {
        os << this->TextIndent;
        os.write(l, static_cast<long>(r-l));
        column = static_cast<long>(r-l);
        newSentence = (*(r-1) == '.');
        }
      }
    
    // Move to beginning of next word.  Skip over whitespace.
    l = r;
    while(*l && (*l == ' ')) { ++l; }    
    }
}

//----------------------------------------------------------------------------
void cmDocumentation::PrintHTMLEscapes(std::ostream& os, const char* text)
{
  static cmDocumentationEntry escapes[] =
  {
    {"<", "&lt;", 0},
    {">", "&gt;", 0},
    {"&", "&amp;", 0},
    {"\n", "<br>", 0},
    {0,0,0}
  };
  for(const char* p = text; *p; ++p)
    {
    bool found = false;
    for(const cmDocumentationEntry* op = escapes; !found && op->name; ++op)
      {
      if(op->name[0] == *p)
        {
        os << op->brief;
        found = true;
        }
      }
    if(!found)
      {
      os << *p;
      }
    }
}

//----------------------------------------------------------------------------
void cmDocumentation::PrintDocumentationUsage(std::ostream& os)
{
  this->CreateUsageDocumentation();
  this->Print(UsageForm, os);
}

//----------------------------------------------------------------------------
void cmDocumentation::PrintDocumentationFull(std::ostream& os)
{
  this->CreateFullDocumentation();
  this->Print(TextForm, os);
}

//----------------------------------------------------------------------------
void cmDocumentation::PrintDocumentationHTML(std::ostream& os)
{
  this->CreateFullDocumentation();
  os << "<html><body>\n";
  this->Print(HTMLForm, os);
  os << "</body></html>\n";
}

//----------------------------------------------------------------------------
void cmDocumentation::PrintDocumentationMan(std::ostream& os)
{
  this->CreateManDocumentation();
  os << ".TH CMake 1 \""
     << cmSystemTools::GetCurrentDateTime("%B %d, %Y").c_str()
     << "\" \"CMake " CMake_VERSION_STRING "\"\n";
  this->Print(ManForm, os);
}

//----------------------------------------------------------------------------
void cmDocumentation::CreateUsageDocumentation()
{
  this->ClearSections();
  if(!this->NameSection.empty())
    {
    this->AddSection("Name", &this->NameSection[0]);
    }
  if(!this->UsageSection.empty())
    {
    this->AddSection("Usage", &this->UsageSection[0]);
    }
  if(!this->OptionsSection.empty())
    {
    this->AddSection("Command-Line Options", &this->OptionsSection[0]);
    }
}

//----------------------------------------------------------------------------
void cmDocumentation::CreateFullDocumentation()
{
  this->ClearSections();
  if(!this->NameSection.empty())
    {
    this->AddSection("Name", &this->NameSection[0]);
    }
  if(!this->UsageSection.empty())
    {
    this->AddSection("Usage", &this->UsageSection[0]);
    }
  if(!this->DescriptionSection.empty())
    {
    this->AddSection(0, &this->DescriptionSection[0]);
    }
  if(!this->OptionsSection.empty())
    {
    this->AddSection("Command-Line Options", &this->OptionsSection[0]);
    }
  if(!this->CommandsSection.empty())
    {
    this->AddSection("Listfile Commands", &this->CommandsSection[0]);
    }
  this->AddSection("Copyright", cmDocumentationCopyright);
  this->AddSection("Mailing List", cmDocumentationMailingList);
}

//----------------------------------------------------------------------------
void cmDocumentation::CreateManDocumentation()
{
  this->ClearSections();
  if(!this->NameSection.empty())
    {
    this->AddSection("NAME", &this->NameSection[0]);
    }
  if(!this->UsageSection.empty())
    {
    this->AddSection("SYNOPSIS", &this->UsageSection[0]);
    }
  if(!this->DescriptionSection.empty())
    {
    this->AddSection("DESCRIPTION", &this->DescriptionSection[0]);
    }
  if(!this->OptionsSection.empty())
    {
    this->AddSection("OPTIONS", &this->OptionsSection[0]);
    }
  if(!this->CommandsSection.empty())
    {
    this->AddSection("COMMANDS", &this->CommandsSection[0]);
    }
  this->AddSection("COPYRIGHT", cmDocumentationCopyright);
  this->AddSection("MAILING LIST", cmDocumentationMailingList);
  this->AddSection("AUTHOR", cmDocumentationAuthor);
}

//----------------------------------------------------------------------------
void cmDocumentation::SetSection(const cmDocumentationEntry* header,
                                 const cmDocumentationEntry* section,
                                 const cmDocumentationEntry* footer,
                                 std::vector<cmDocumentationEntry>& vec)
{
  vec.erase(vec.begin(), vec.end());
  if(header)
    {
    for(const cmDocumentationEntry* op = header; op->brief; ++op)
      {
      vec.push_back(*op);
      }
    }
  if(section)
    {
    for(const cmDocumentationEntry* op = section; op->brief; ++op)
      {
      vec.push_back(*op);
      }
    }
  if(footer)
    {
    for(const cmDocumentationEntry* op = footer; op->brief; ++op)
      {
      vec.push_back(*op);
      }
    }
  cmDocumentationEntry empty = {0,0,0};
  vec.push_back(empty);  
}
