/* 
 * Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; version 2 of the
 * License.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301  USA
 */

#include <glib/gstdio.h>

#include "wb_model.h"
#include "reporting.h"

#include "interfaces/sqlgenerator.h"
#include "grts/structs.workbench.h"

#include "reporting_template_variables.h"

#include "base/util_functions.h"
#include "base/string_utilities.h"
#include "base/geometry.h"
#include "base/log.h"

#include "mforms/code_editor.h"

// Support for syntax highlighting in SQL output.
#ifdef _WIN32
  #include "ScintillaWR.h"
  #define SCI_WRAPPER_NS ScintillaWrapper::
#else
  #include "Scintilla.h"
  #include "WordList.h"
  #include "LexerModule.h"
  #include "LexAccessor.h"
  #include "Accessor.h"
  #include "Catalogue.h"
  #include "PropSetSimple.h"
  #define SCI_WRAPPER_NS Scintilla::
#endif
#include "SciLexer.h"

using namespace std;
using namespace base;

using ctemplate::Template;
using ctemplate::TemplateDictionary;

DEFAULT_LOG_DOMAIN("Reporting")

//----------------- LexerDocument ------------------------------------------------------------------

LexerDocument::LexerDocument(const std::string& text)
: _text(text)
{
  _style_position = 0;
  _style_buffer = new char[_text.size()];

  // Split the text into lines and store the index where a line starts.
  std::vector<std::string> lines = base::split(text, "\n");
  int start = 0;
  for (size_t i = 0; i < lines.size(); ++i)
  {
    _line_starts.push_back(start);
    start += lines[i].size() + 1;
  }
}

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

LexerDocument::~LexerDocument()
{
  delete[] _style_buffer;
}

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

// IDocument implementation.
int LexerDocument::Version() const
{
  return 0; // Indicates old style lexer document.
}

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

void LexerDocument::SetErrorStatus(int status)
{
  throw std::logic_error(std::string("Internal error. Unexpected use of unimplemented function ").
    append(__FUNCTION__).append(" in LexerDocument (").
    append(__FILE__).append(")."));
}

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

int LexerDocument::Length() const
{
  return _text.size();
}

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

void LexerDocument::GetCharRange(char *buffer, int position, int lengthRetrieve) const
{
  _text.copy(buffer, lengthRetrieve, position);
}

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

char LexerDocument::StyleAt(int position) const
{
  return _style_buffer[position];
}

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

int LexerDocument::LineFromPosition(int position) const
{
  // Since we always style the full text starting from position 0 this should be the normal case.
  if (position == 0)
    return 0;

  throw std::logic_error(std::string("Internal error. Unexpected use of unimplemented function ").
    append(__FUNCTION__).append(" in LexerDocument (").
    append(__FILE__).append(")."));
}

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

int LexerDocument::LineStart(int line) const
{
  if (line < 0 || line > (int)_line_starts.size())
    return 0;
  return _line_starts[line];
}

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

int LexerDocument::GetLevel(int line) const
{
  if (line < 0 || line > (int) _level_cache.size() - 1)
    return SC_FOLDLEVELBASE;
  return _level_cache[line];
}

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

int LexerDocument::SetLevel(int line, int level)
{
  if (line >= 0)
  {
    // Check if we need to make more room in our cache.
    if (line >= (int) _level_cache.size())
    {
      int last_size= _level_cache.size();
      _level_cache.resize(line + 1);
      
      // Initialize newly added entries.
      for (int i= last_size - 1; i < (int) _level_cache.size() - 1; i++)
        _level_cache[i]= SC_FOLDLEVELBASE;
    }
    _level_cache[line]= level;
    return level;
  }
  return SC_FOLDLEVELBASE;
}

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

int LexerDocument::GetLineState(int line) const
{
  throw std::logic_error(std::string("Internal error. Unexpected use of unimplemented function ").
    append(__FUNCTION__).append(" in LexerDocument (").
    append(__FILE__).append(")."));
}

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

int LexerDocument::SetLineState(int line, int state)
{
  throw std::logic_error(std::string("Internal error. Unexpected use of unimplemented function ").
    append(__FUNCTION__).append(" in LexerDocument (").
    append(__FILE__).append(")."));
}

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

void LexerDocument::StartStyling(int position, char mask)
{
  _styling_mask = mask;
  _style_position = position;
}

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

bool LexerDocument::SetStyleFor(int length, char style)
{
  // Style buffer and text have the same length so we can use the text to get the size (which is faster).
  if (_style_position + length >= (int) _text.size())
    return false;
  
  int i= _style_position;
  style &= _styling_mask;
  for (; length > 0; i++, length--)
    _style_buffer[i]= style;
  _style_position= i;
  
  return true;
}

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

bool LexerDocument::SetStyles(int length, const char *styles)
{
  if (_style_position + length > (int) _text.size())
    return false;
  
  int i= _style_position;
  for (int j= 0; length > 0; i++, j++, length--)
    _style_buffer[i]= styles[j] & _styling_mask;
  _style_position= i;
  
  return true;
}

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

void LexerDocument::DecorationSetCurrentIndicator(int indicator)
{
  throw std::logic_error(std::string("Internal error. Unexpected use of unimplemented function ").
    append(__FUNCTION__).append(" in LexerDocument (").
    append(__FILE__).append(")."));
}

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

void LexerDocument::DecorationFillRange(int position, int value, int fillLength)
{
  throw std::logic_error(std::string("Internal error. Unexpected use of unimplemented function ").
    append(__FUNCTION__).append(" in LexerDocument (").
    append(__FILE__).append(")."));
}

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

void LexerDocument::ChangeLexerState(int start, int end)
{
  throw std::logic_error(std::string("Internal error. Unexpected use of unimplemented function ").
    append(__FUNCTION__).append(" in LexerDocument (").
    append(__FILE__).append(")."));
}

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

int LexerDocument::CodePage() const
{
  return SC_CP_UTF8;
}

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

bool LexerDocument::IsDBCSLeadByte(char ch) const
{
  return false;
}

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

const char* LexerDocument::BufferPointer()
{
  throw std::logic_error(std::string("Internal error. Unexpected use of unimplemented function ").
    append(__FUNCTION__).append(" in LexerDocument (").
    append(__FILE__).append(")."));
}

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

int LexerDocument::GetLineIndentation(int line)
{
  throw std::logic_error(std::string("Internal error. Unexpected use of unimplemented function ").
    append(__FUNCTION__).append(" in LexerDocument (").
    append(__FILE__).append(")."));
}

//----------------- WbModelImpl --------------------------------------------------------------------

/**
 * Initializes the template engine with our templates. They are registered so we can get error
 * reports for them if something is wrong and also can reload them if they have been changed while 
 * the application is running.
 */
void WbModelImpl::initializeReporting()
{
  // Enumerate reporting folder for all stored report types.
}

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

/*
 * @brief returns the list of available template file directories
 *
 * @param model - the workbench_physical_ModelRef to process
 * @param templates - a GRT List the available templates will be added to
 * @return 1 on success, 0 on error
 */
int WbModelImpl::getAvailableReportingTemplates(grt::StringListRef templates)
{
  // get pointer to the GRT
  grt::GRT *grt= get_grt();
  string basedir= bec::GRTManager::get_instance_for(grt)->get_basedir();
  string template_base_dir= bec::make_path(basedir, "modules/data/wb_model_reporting");
  GDir *dir;
  const char *entry;

  dir= g_dir_open(template_base_dir.c_str(), 0, NULL);
  if (dir)
  {
    while ((entry= g_dir_read_name(dir)) != NULL)
    {
      char *path= g_build_filename(template_base_dir.c_str(), entry, NULL);

      if (g_file_test(path, (GFileTest)(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)) && 
        g_str_has_suffix(entry, ".tpl"))
      {
        // reformat the template name, replace _ with spaces
        char *temp= g_strdup(entry);
        char *ptr= temp; 
        while ((ptr= strchr(ptr, '_'))) 
          *ptr= ' ';

        // remove .tpl suffix
        ptr= strrchr(temp, '.'); *ptr= 0;

        templates.insert(temp);

        g_free(temp);
      }

      g_free(path);
    }

    g_dir_close(dir);
  }

  return 1;
}

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

/*
 * @brief returns the template info for the given template
 *
 * @param model - the workbench_physical_ModelRef to process
 * @param template_name - the name of the template
 * @return the template info object
 */
workbench_model_reporting_TemplateInfoRef WbModelImpl::getReportingTemplateInfo(const string& template_name)
{
  string template_dir= getTemplateDirFromName(template_name);

  string template_info_path= bec::make_path(template_dir, "info.xml");
  if (g_file_test(template_info_path.c_str(), (GFileTest)(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR)))
    return workbench_model_reporting_TemplateInfoRef::cast_from(get_grt()->unserialize(template_info_path));
  else
    return workbench_model_reporting_TemplateInfoRef();
}

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

workbench_model_reporting_TemplateStyleInfoRef WbModelImpl::get_template_style_from_name(grt::GRT *grt, 
                                                                         string template_name, string template_style_name)
{
  if (template_style_name == "")
    return workbench_model_reporting_TemplateStyleInfoRef();

  string template_dir= getTemplateDirFromName(template_name);

  string template_info_path= bec::make_path(template_dir, "info.xml");
  if (g_file_test(template_info_path.c_str(), (GFileTest)(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR)))
  {
    workbench_model_reporting_TemplateInfoRef info= 
      workbench_model_reporting_TemplateInfoRef::cast_from(grt->unserialize(template_info_path));

    for( size_t i= 0; i < info->styles().count(); i++)
    {
      workbench_model_reporting_TemplateStyleInfoRef styleInfo=
        info->styles().get(i);

      if (template_style_name == (string)styleInfo->name())
        return styleInfo;
    }
  }

  return workbench_model_reporting_TemplateStyleInfoRef();
}

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

void read_option(bool& var, const char* field, const grt::DictRef& dict)
{
  if (dict.has_key(field))
    var= dict.get_int(field) != 0;
}

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

void read_option(string& var, const char* field, const grt::DictRef& dict)
{
  if (dict.has_key(field))
    var= dict.get_string(field);
}

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

/**
 * Assigns the given value to the dictionary if it is not empty. Otherwise the text "n/a" is added.
 **/
void assignValueOrNA(TemplateDictionary* dict, const char* key, const string& value)
{
  if (value.size() == 0)
    dict->SetValue(key, "<span class=\"report_na_entry\">n/a</span>");
  else
    dict->SetValue(key, value);
}

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

void fillTablePropertyDict(const db_mysql_TableRef& table, TemplateDictionary* table_dict)
{
  assignValueOrNA(table_dict, REPORT_TABLE_AVG_ROW_LENGTH, *table->avgRowLength());
  table_dict->SetValue(REPORT_TABLE_USE_CHECKSUM, (table->checksum() == 1) ? "yes" : "no");
  assignValueOrNA(table_dict, REPORT_TABLE_CONNECTION_STRING, *table->connectionString());
  assignValueOrNA(table_dict, REPORT_TABLE_CHARSET, *table->defaultCharacterSetName());
  assignValueOrNA(table_dict, REPORT_TABLE_COLLATION, *table->defaultCollationName());
  table_dict->SetValue(REPORT_TABLE_DELAY_KEY_UPDATES, (table->delayKeyWrite() == 1) ? "yes" : "no");
  assignValueOrNA(table_dict, REPORT_TABLE_MAX_ROW_COUNT, *table->maxRows());
  assignValueOrNA(table_dict, REPORT_TABLE_MIN_ROW_COUNT, *table->minRows());
  assignValueOrNA(table_dict, REPORT_TABLE_UNION_TABLES, *table->mergeUnion());
  assignValueOrNA(table_dict, REPORT_TABLE_MERGE_METHOD, *table->mergeInsert());
  assignValueOrNA(table_dict, REPORT_TABLE_AUTO_INCREMENT, *table->nextAutoInc());
  assignValueOrNA(table_dict, REPORT_TABLE_PACK_KEYS, *table->packKeys());
  table_dict->SetValue(REPORT_TABLE_HAS_PASSWORD, ((*table->password()).size() == 0) ? "no" : "yes");
  assignValueOrNA(table_dict, REPORT_TABLE_ROW_FORMAT, *table->rowFormat());
  assignValueOrNA(table_dict, REPORT_TABLE_KEY_BLOCK_SIZE, *table->keyBlockSize());
  assignValueOrNA(table_dict, REPORT_TABLE_DATA_DIR, *table->tableDataDir());
  assignValueOrNA(table_dict, REPORT_TABLE_INDEX_DIR, *table->tableIndexDir());
  assignValueOrNA(table_dict, REPORT_TABLE_ENGINE, *table->tableEngine());
  
  // Partitions.
  if (table->partitionCount() > 0)
  {
    TemplateDictionary* partitions_dict= table_dict->AddSectionDictionary(REPORT_PARTITION_LISTING);
    partitions_dict->SetIntValue(REPORT_PARTITION_COUNT, table->partitionCount());
    partitions_dict->SetValue(REPORT_PARTITION_TYPE, *table->partitionType());
    partitions_dict->SetValue(REPORT_PARTITION_EXPRESSION, *table->partitionExpression());
    
    partitions_dict->SetIntValue(REPORT_PARTITION_SUB_COUNT, table->subpartitionCount());
    partitions_dict->SetValue(REPORT_PARTITION_SUB_TYPE, *table->subpartitionType());
    partitions_dict->SetValue(REPORT_PARTITION_SUB_EXPRESSION, *table->subpartitionExpression());

    for (int i= 0; i < table->partitionCount(); i++)
    {
      db_mysql_PartitionDefinitionRef partition= table->partitionDefinitions().get(i);
    
      TemplateDictionary* partition_dict= table_dict->AddSectionDictionary(REPORT_PARTITIONS);
      
      partition_dict->SetValue(REPORT_PARTITION_NAME, *partition->name());
      assignValueOrNA(partition_dict, REPORT_PARTITION_VALUE, *partition->value());
      partition_dict->SetValue(REPORT_PARTITION_COMMENT, *partition->comment());
      assignValueOrNA(partition_dict, REPORT_PARTITION_MAX_ROW_COUNT, *partition->maxRows());
      assignValueOrNA(partition_dict, REPORT_PARTITION_MIN_ROW_COUNT, *partition->minRows());
      assignValueOrNA(partition_dict, REPORT_PARTITION_DATA_DIR, *partition->indexDirectory());
      assignValueOrNA(partition_dict, REPORT_PARTITION_INDEX_DIR, *partition->dataDirectory());
      
      // One possible iteration to sub partitions.
       for (int j= 0; j < table->subpartitionCount(); j++)
      {
        db_mysql_PartitionDefinitionRef sub_partition= partition->subpartitionDefinitions().get(j);
        
        TemplateDictionary* sub_partition_dict= partition_dict->AddSectionDictionary(REPORT_PARTITION_SUB_PARTITIONS);
        
        sub_partition_dict->SetValue(REPORT_PARTITION_SUB_NAME, *sub_partition->name());
        assignValueOrNA(sub_partition_dict, REPORT_PARTITION_SUB_VALUE, *sub_partition->value());
        sub_partition_dict->SetValue(REPORT_PARTITION_SUB_COMMENT, *sub_partition->comment());
        assignValueOrNA(sub_partition_dict, REPORT_PARTITION_SUB_MAX_ROW_COUNT, *sub_partition->maxRows());
        assignValueOrNA(sub_partition_dict, REPORT_PARTITION_SUB_MIN_ROW_COUNT, *sub_partition->minRows());
        assignValueOrNA(sub_partition_dict, REPORT_PARTITION_SUB_DATA_DIR, *sub_partition->indexDirectory());
        assignValueOrNA(sub_partition_dict, REPORT_PARTITION_SUB_INDEX_DIR, *sub_partition->dataDirectory());
      }
    }
  }
}

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

void fillColumnDict(const db_mysql_ColumnRef& col, const db_mysql_TableRef& table, TemplateDictionary *col_dict, bool detailed)
{
  if (*table->isPrimaryKeyColumn(col))
  {
    if (*table->isForeignKeyColumn(col))
      col_dict->SetValue(REPORT_COLUMN_KEY, "FK");
    else
      col_dict->SetValue(REPORT_COLUMN_KEY, "PK");
  }

  col_dict->SetValue(REPORT_COLUMN_NAME, *col->name());

  col_dict->SetValue(REPORT_COLUMN_NOTNULL, (col->isNotNull() == 1) ? "Yes" : "No");
  col_dict->SetValue(REPORT_COLUMN_DEFAULTVALUE, (col->defaultValueIsNull() == 1) ? "NULL" : 
    *col->defaultValue());
  col_dict->SetValue(REPORT_COLUMN_COMMENT, *col->comment());
  col_dict->SetValue(REPORT_COLUMN_DATATYPE, *col->formattedRawType());

  if (detailed)
  {
    col_dict->SetValue(REPORT_TABLE_NAME, *table->name());
    
    string key_part= "";
    if (table->isPrimaryKeyColumn(col))
      key_part += "Primary key, ";
    if (table->isForeignKeyColumn(col))
      key_part += "Foreign key, ";

    col_dict->SetValue(REPORT_COLUMN_KEY_PART, key_part.substr(0, key_part.size() - 2));
    col_dict->SetValue(REPORT_COLUMN_NULLABLE, (col->isNotNull() == 1) ? "No" : "Yes");
    col_dict->SetValue(REPORT_COLUMN_AUTO_INC, (col->autoIncrement() == 1) ? "Yes" : "No");
    if (!col->characterSetName().empty())
      col_dict->SetValue(REPORT_COLUMN_CHARSET, *col->characterSetName());
    else
      col_dict->SetValue(REPORT_COLUMN_CHARSET, "Schema Default");
    
    if (!col->collationName().empty())
      col_dict->SetValue(REPORT_COLUMN_COLLATION, *col->collationName());
    else
      col_dict->SetValue(REPORT_COLUMN_COLLATION, "Schema Default");

    if (col->userType().is_valid())
      col_dict->SetValue(REPORT_COLUMN_IS_USERTYPE, "Yes");
    else 
      col_dict->SetValue(REPORT_COLUMN_IS_USERTYPE, "No");
  }
}

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

void fillIndexDict(const db_mysql_IndexRef& idx, const db_mysql_TableRef& table, TemplateDictionary *idx_dict, bool detailed)
{
  idx_dict->SetValue(REPORT_INDEX_NAME, *idx->name());

  idx_dict->SetValue(REPORT_INDEX_PRIMARY, (idx->isPrimary() == 1) ? "Yes" : "No");
  idx_dict->SetValue(REPORT_INDEX_UNIQUE, (idx->unique() == 1) ? "Yes" : "No");
  idx_dict->SetValue(REPORT_INDEX_TYPE, *idx->indexType());
  idx_dict->SetValue(REPORT_INDEX_KIND, *idx->indexKind());
  idx_dict->SetValue(REPORT_INDEX_COMMENT, *idx->comment());

  for (size_t l= 0; l < idx->columns().count(); l++)
  {
    db_mysql_IndexColumnRef idx_col= idx->columns().get(l);

    TemplateDictionary *idx_col_dict= idx_dict->AddSectionDictionary(REPORT_INDEX_COLUMNS);
    idx_col_dict->SetValue(REPORT_INDEX_COLUMN_NAME, *idx_col->referencedColumn()->name());
    idx_col_dict->SetValue(REPORT_INDEX_COLUMN_ORDER, (idx_col->descend() == 1) ? "Descending" : "Ascending");
    if (idx_col->comment().empty())
      idx_col_dict->SetValue(REPORT_INDEX_COLUMN_COMMENT, "no comment");
    else
      idx_col_dict->SetValue(REPORT_INDEX_COLUMN_COMMENT, *idx_col->comment());
  }

  if (detailed)
  {
    idx_dict->SetValue(REPORT_TABLE_NAME, *table->name());
    idx_dict->SetIntValue(REPORT_INDEX_KEY_BLOCK_SIZE, idx->keyBlockSize());
  }
}

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

void fillForeignKeyDict(const db_mysql_ForeignKeyRef& fk, const db_mysql_TableRef& table, TemplateDictionary *fk_dict, bool detailed)
{
  fk_dict->SetValue(REPORT_REL_NAME, *fk->name());
  fk_dict->SetValue(REPORT_REL_TYPE, bec::TableHelper::is_identifying_foreign_key(table, fk) ? "Identifying" : "Non-Identifying");
  if (fk->referencedTable().is_valid())
    fk_dict->SetValue(REPORT_REL_PARENTTABLE, *fk->referencedTable()->name());
  fk_dict->SetValue(REPORT_REL_CHILDTABLE, *table->name());
  fk_dict->SetValue(REPORT_REL_CARD, (fk->many() == 1) ? "1:n" : "1:1");

  if (detailed)
  {
    fk_dict->SetValue(REPORT_TABLE_NAME, *table->name());
    fk_dict->SetValue(REPORT_FK_DELETE_RULE, *fk->deleteRule());
    fk_dict->SetValue(REPORT_FK_UPDATE_RULE, *fk->updateRule());
    fk_dict->SetValue(REPORT_FK_MANDATORY, fk->mandatory() ? "Yes" : "No");
  }
}

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

void fillTriggerDict(const db_mysql_TriggerRef& trigger, const db_mysql_TableRef& table, 
                     TemplateDictionary *trigger_dict)
{
  trigger_dict->SetValue(REPORT_TRIGGER_NAME, *trigger->name());
  trigger_dict->SetValue(REPORT_TRIGGER_TIMING, *trigger->timing());
  trigger_dict->SetValue(REPORT_TRIGGER_CONDITION, *trigger->condition());
  trigger_dict->SetValue(REPORT_TRIGGER_ENABLED, (trigger->enabled() == 1) ? "yes" : "no");
  
  trigger_dict->SetValue(REPORT_TABLE_NAME, table->name().c_str());
  trigger_dict->SetValue(REPORT_TRIGGER_DEFINER, *trigger->definer());
  trigger_dict->SetValue(REPORT_TRIGGER_EVENT, *trigger->event());
  trigger_dict->SetIntValue(REPORT_TRIGGER_ORDER, *trigger->order());
  trigger_dict->SetIntValue(REPORT_TRIGGER_CONDITION, *trigger->order());
  trigger_dict->SetValue(REPORT_TRIGGER_TIMING, *trigger->timing());
  trigger_dict->SetValue(REPORT_TRIGGER_ORIENTATION, *trigger->orientation());
  trigger_dict->SetValue(REPORT_TRIGGER_REFERENCE_NEW_ROW, *trigger->referenceNewRow());
  trigger_dict->SetValue(REPORT_TRIGGER_REFERENCE_NEW_TABLE, *trigger->referenceNewTable());
  trigger_dict->SetValue(REPORT_TRIGGER_REFERENCE_OLD_ROW, *trigger->referenceOldRow());
  trigger_dict->SetValue(REPORT_TRIGGER_REFERENCE_OLD_TABLE, *trigger->referenceOldTable());
}

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

void fillViewDict(const db_mysql_ViewRef& view, TemplateDictionary *view_dict)
{
  view_dict->SetValue(REPORT_VIEW_NAME, *view->name());
  view_dict->SetValueAndShowSection(REPORT_VIEW_COMMENT, *view->comment(), REPORT_VIEW_COMMENT_LISTING);
  
  view_dict->SetValue(REPORT_VIEW_COLUMNS, *view->name());
  view_dict->SetValue(REPORT_VIEW_READ_ONLY, view->isReadOnly() ? "read only" : "writable");
  view_dict->SetValue(REPORT_VIEW_WITH_CHECK, view->withCheckCondition() ? "yes" : "no");
  
  string columns= "";
  for (grt::StringListRef::const_iterator iterator= view->columns().begin(); 
      iterator != view->columns().end(); iterator++)
  {
    columns += *iterator;
    columns += ", ";
  }
  assignValueOrNA(view_dict, REPORT_VIEW_COLUMNS, columns);
}

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

void fillRoutineDict(const db_mysql_RoutineRef& routine, TemplateDictionary *routine_dict)
{
  string value;

  routine_dict->SetValue(REPORT_ROUTINE_NAME, *routine->name());
  routine_dict->SetValue(REPORT_ROUTINE_TYPE, *routine->routineType());
  
  assignValueOrNA(routine_dict, REPORT_ROUTINE_RETURN_TYPE, routine->returnDatatype());
  assignValueOrNA(routine_dict, REPORT_ROUTINE_SECURITY, value= routine->security());

  routine_dict->SetIntValue(REPORT_ROUTINE_PARAMETER_COUNT, routine->params().count());
  for (int j= 0; j < (int)routine->params().count(); j++)
  {
    db_mysql_RoutineParamRef parameter= routine->params().get(j);
    
    TemplateDictionary *parameter_dict= routine_dict->AddSectionDictionary(REPORT_ROUTINE_PARAMETERS);
    
    parameter_dict->SetValue(REPORT_ROUTINE_PARAMETER_NAME, *parameter->name());
    parameter_dict->SetValue(REPORT_ROUTINE_PARAMETER_TYPE, *parameter->paramType());
    parameter_dict->SetValue(REPORT_ROUTINE_PARAMETER_DATA_TYPE, *parameter->datatype());
  }
}

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

static Scintilla::WordList* keywordLists[KEYWORDSET_MAX + 2];

/**
 * Initialization of the syntax highlighter classes that are used to colorize SQL code in HTML reports.
 */
const Scintilla::LexerModule* setup_syntax_highlighter(db_mgmt_RdbmsRef rdbms)
{
  const Scintilla::LexerModule* result= SCI_WRAPPER_NS Catalogue::Find("mysql");

  if (result != NULL)
  {
    mforms::CodeEditorConfig config(mforms::LanguageMySQL);
    std::map<std::string, std::string> keywords = config.get_keywords();
    
    // Create the keyword lists used for lexing.
	  for (int i = 0; i <= KEYWORDSET_MAX; i++)
      keywordLists[i] = new SCI_WRAPPER_NS WordList();
    keywordLists[KEYWORDSET_MAX + 1] = NULL;
    
    // There are no predefined constants for the indices below, but the occupancy of the list array
    // can be seen in LexMySQL.cxx.
    ((SCI_WRAPPER_NS WordList*) keywordLists[0])->Set(keywords["Major Keywords"].c_str());
    ((SCI_WRAPPER_NS WordList*) keywordLists[3])->Set(keywords["Functions"].c_str());
    ((SCI_WRAPPER_NS WordList*) keywordLists[5])->Set(keywords["Procedure keywords"].c_str());
    ((SCI_WRAPPER_NS WordList*) keywordLists[6])->Set(keywords["User Keywords 1"].c_str());

    // TODO: adjust CSS files so the color values correspond to what the user has set currently.
  }

  return result;
}

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

/**
 * Cleanup after we are done with report creation.
 */
void cleanup_syntax_highlighter()
{
	for (int i = 0; i <= KEYWORDSET_MAX; i++)
    delete (SCI_WRAPPER_NS WordList*) keywordLists[i];
}

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

/**
 * Returns the HTML markup for the given style.
 */
const string markupFromStyle(int style)
{
  switch (style)
  {
    case SCE_MYSQL_DEFAULT:
      return "<span class=\"syntax_default\">%s</span>";
      break;
    case SCE_MYSQL_COMMENT:
      return "<span class=\"syntax_comment\">%s</span>";
      break;
    case SCE_MYSQL_COMMENTLINE:
      return "<span class=\"syntax_comment_line\">%s</span>";
      break;
    case SCE_MYSQL_VARIABLE:
      return "<span class=\"syntax_variable\">%s</span>";
      break;
    case SCE_MYSQL_SYSTEMVARIABLE:
      return "<span class=\"syntax_system_variable\">%s</span>";
      break;
    case SCE_MYSQL_KNOWNSYSTEMVARIABLE:
      return "<span class=\"syntax_known_system_variable\">%s</span>";
      break;
    case SCE_MYSQL_NUMBER:
      return "<span class=\"syntax_number\">%s</span>";
      break;
    case SCE_MYSQL_MAJORKEYWORD:
      return "<span class=\"syntax_major_keyword\">%s</span>";
      break;
    case SCE_MYSQL_KEYWORD:
      return "<span class=\"syntax_keyword\">%s</span>";
      break;
    case SCE_MYSQL_DATABASEOBJECT:
      return "<span class=\"syntax_database_object\">%s</span>";
      break;
    case SCE_MYSQL_PROCEDUREKEYWORD:
      return "<span class=\"syntax_procedure_keyword\">%s</span>";
      break;
    case SCE_MYSQL_STRING:
      return "<span class=\"syntax_string\">%s</span>";
      break;
    case SCE_MYSQL_SQSTRING:
      return "<span class=\"syntax_single_quoted_string\">%s</span>";
      break;
    case SCE_MYSQL_DQSTRING:
      return "<span class=\"syntax_double_quoted_string\">%s</span>";
      break;
    case SCE_MYSQL_OPERATOR:
      return "<span class=\"syntax_operator\">%s</span>";
      break;
    case SCE_MYSQL_FUNCTION:
      return "<span class=\"syntax_function\">%s</span>";
      break;
    case SCE_MYSQL_IDENTIFIER:
      return "<span class=\"syntax_identifier\">%s</span>";
      break;
    case SCE_MYSQL_QUOTEDIDENTIFIER:
      return "<span class=\"syntax_quoted_identifier\">%s</span>";
      break;
    case SCE_MYSQL_USER1:
      return "<span class=\"syntax_user1\">%s</span>";
      break;
    case SCE_MYSQL_USER2:
      return "<span class=\"syntax_user2\">%s</span>";
      break;
    case SCE_MYSQL_USER3:
      return "<span class=\"syntax_user3\">%s</span>";
      break;
    case SCE_MYSQL_HIDDENCOMMAND:
      return "<span class=\"syntax_hidden_command\">%s</span>";
      break;
    default:
      return "%s";
  }
}

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

void set_ddl(TemplateDictionary *target, SQLGeneratorInterfaceImpl* sqlgenModule, 
             const GrtNamedObjectRef& object, const Scintilla::LexerModule* lexer,
             bool ddl_enabled)
{
  if (ddl_enabled && sqlgenModule != NULL)
  {
    string sql= sqlgenModule->makeCreateScriptForObject(object);
    
    if (lexer != NULL)
    {
      // Add syntax highlighter markup.
      LexerDocument* document= new LexerDocument(sql);
      SCI_WRAPPER_NS PropSetSimple property_set;
      SCI_WRAPPER_NS Accessor* accessor= new SCI_WRAPPER_NS Accessor(document, &property_set);
      
      lexer->Lex(0, sql.size(), 0, keywordLists, *accessor);
      
      int currentStyle= SCE_MYSQL_DEFAULT;
      int tokenStart= 0;
      string markup= "";
      int i;
      for (i= 0; i < (int) sql.size(); i++)
        if (currentStyle != accessor->StyleAt(i))
        {
          markup += bec::replace_string(markupFromStyle(currentStyle), "%s", sql.substr(tokenStart, i - tokenStart));
          tokenStart= i;
          currentStyle= accessor->StyleAt(i);
        }
      
      markup += bec::replace_string(markupFromStyle(currentStyle), "%s", sql.substr(tokenStart, i - tokenStart));
      
      delete accessor;
      delete document;
      
      sql= markup;
    };
    
    string fixed_line_breaks= bec::replace_string(sql, "\n", "<br />");

    // The DDL script is wrapped in an own section dir to allow switching it off entirely (including
    // the surrounding HTML code).
    target->SetValueAndShowSection(REPORT_DDL_SCRIPT, fixed_line_breaks, REPORT_DDL_LISTING);
  }
}

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

static int count_template_files(const string template_dir)
{  
  // loop over all files in the template dir
  const char *entry;
  int count= 0;
  GDir *dir= g_dir_open(template_dir.c_str(), 0, NULL);
  if (dir)
  {
    while ((entry= g_dir_read_name(dir)) != NULL)
    {      
      // skip the info.xml file and preview pngs
      if (strcmp(entry, "info.xml") == 0 || 
          (g_str_has_prefix(entry, "preview_") && g_str_has_suffix(entry, ".png")))
        continue;
     
      char *path= g_build_filename(template_dir.c_str(), entry, NULL);
      if (g_file_test(path, (GFileTest)(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR)))
      {
        if (g_str_has_suffix(entry, ".tpl"))
          count++;
      }
      g_free(path);
    }
  }
  g_dir_close(dir);
  return count;
}

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

/**
 * @brief Generates a schema report for the model passed in workbench_physical_Model.
 *
 * @param model - the workbench_physical_ModelRef to process
 * @param options - various options that customize the output, including output template, output path etc.
 * @return 1 on success, 0 on error
 */
int WbModelImpl::generateReport(workbench_physical_ModelRef model, const grt::DictRef& options)
{
  // get pointer to the GRT
  grt::GRT *grt= model.get_grt();
  string basedir= bec::GRTManager::get_instance_for(grt)->get_basedir();
  string template_base_dir= bec::make_path(basedir, "modules/data/wb_model_reporting");

  db_mysql_CatalogRef catalog= db_mysql_CatalogRef::cast_from(model->catalog());

  // helper variables
  map<string, vector<db_mysql_ForeignKeyRef> > tbl_fk_map;

  // Process options
  string template_name= "HTML Basic Frames";
  string template_style_name= "";
  string title= "MySQL Model Report";
  string output_path= "";
  bool columns_show= true;
  bool indices_show= true;
  bool fks_show= true;
  bool fks_show_referred_fks= true;
  bool show_ddl= true;
  bool use_highlighting= false;
  
  if (options.is_valid())
  {
    read_option(template_name, "template_name", options);
    read_option(template_style_name, "template_style_name", options);
    read_option(title, "title", options);
    read_option(output_path, "output_path", options);
    read_option(columns_show, "columns_show", options);
    read_option(indices_show, "indices_show", options);
    read_option(fks_show, "fks_show", options);
    read_option(fks_show_referred_fks, "fks_show_referred_fks", options);
    read_option(show_ddl, "show_ddl", options);
    read_option(use_highlighting, "use_highlighting", options);
  }
  
  bool single_file_report = count_template_files(getTemplateDirFromName(template_name)) == 1;
  
  const Scintilla::LexerModule* lexer= NULL;
  if (use_highlighting)
    lexer= setup_syntax_highlighter(model->rdbms());
  
  // Ensure the output dir exists.
  {
    int r;

    if (!g_file_test(output_path.c_str(), G_FILE_TEST_EXISTS))
    {
      r = g_mkdir_with_parents(output_path.c_str(), 0700);
      if (r < 0)
      {
        grt->send_error(strfmt("Could not create report directory %s: %s", output_path.c_str(),
          g_strerror(errno)));
        grt->make_output_visible();
        return 0;
      }
    }
  }

  // Start report generation
  grt->send_info("Generating schema report...");

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

  // Build FK dictionary if required
  if (fks_show && fks_show_referred_fks)
  {
    // build schema_dict by loop over all schemata, add it to the main_dict
    for (size_t i= 0; i < catalog->schemata().count(); i++)
    {
      db_mysql_SchemaRef schema= catalog->schemata().get(i);

      // loop over all tables
      for (size_t j= 0; j < schema->tables().count(); j++)
      {
        db_mysql_TableRef table= schema->tables().get(j);

        // loop over all foreign keys
        for (size_t k= 0; k < table->foreignKeys().count(); k++)
        {
          db_mysql_ForeignKeyRef fk= table->foreignKeys().get(k);
          db_mysql_TableRef ref_tbl= fk->referencedTable();

          if (!ref_tbl.is_valid())
            continue;

          // look for table in tbl_fk_map
          map<string, vector<db_mysql_ForeignKeyRef> >::iterator tbl_fk_map_it=
            tbl_fk_map.find(ref_tbl.id());
          if (tbl_fk_map_it != tbl_fk_map.end())
          {
            // if found, add fk reference to table
            tbl_fk_map_it->second.push_back(fk);
          }
          else
          {
            // if table is not found, create entry
            vector<db_mysql_ForeignKeyRef> new_tbl_fk_map_v;

            // add fk reference to table
            new_tbl_fk_map_v.push_back(fk);

            tbl_fk_map.insert(make_pair(ref_tbl.id(), new_tbl_fk_map_v));
          }
        }
      }
    }
  }

  // --------------------------------------------------------------------------------------------
  // create main dictionary that will be used to expand the templates
  TemplateDictionary main_dict("basic index");

  // Set some global project info.
  main_dict.SetValue(REPORT_TITLE, title);

  string time= bec::fmttime(0, DATETIME_FMT);
  main_dict.SetValue(REPORT_GENERATED, time);

  workbench_DocumentRef document= workbench_DocumentRef::cast_from(model->owner());
  main_dict.SetValue(REPORT_PROJECT_NAME, document->info()->project().c_str());
  main_dict.SetValue(REPORT_PROJECT_AUTHOR, document->info()->author().c_str());
  main_dict.SetValue(REPORT_PROJECT_TITLE, document->info()->caption().c_str());
  main_dict.SetValue(REPORT_PROJECT_CHANGED, document->info()->dateChanged().c_str());
  main_dict.SetValue(REPORT_PROJECT_CREATED, document->info()->dateCreated().c_str());
  main_dict.SetValue(REPORT_PROJECT_DESCRIPTION, document->info()->description().c_str());
  main_dict.SetValue(REPORT_PROJECT_VERSION, document->info()->version().c_str());

  workbench_model_reporting_TemplateStyleInfoRef styleInfo= get_template_style_from_name(grt, template_name, template_style_name);
  if (styleInfo.is_valid())
    main_dict.SetValue(REPORT_STYLE_NAME, (string)styleInfo->styleTagValue());

  main_dict.SetIntValue(REPORT_SCHEMA_COUNT, (int)catalog->schemata().count());

  int total_column_count= 0;
  int total_index_count= 0;
  int total_fk_count= 0;
  int total_table_count= 0;
  int total_view_count= 0;
  int total_sp_count= 0;
  int total_trigger_count= 0;

  SQLGeneratorInterfaceImpl *sqlgenModule= NULL;

  if (show_ddl)
  {
    /*
    vector<SQLGeneratorInterfaceImpl*> genmodules= grt->get_implementing_modules<SQLGeneratorInterfaceWrapper>();
    for (vector<SQLGeneratorInterfaceWrapper*>::const_iterator iter= genmodules.begin();
         iter != genmodules.end(); ++iter)
    {
      if ((*iter)->getTargetDBMSName() == "mysql")
      {
        sqlgenModule= *iter;
        break;
      }
    }*/
    sqlgenModule = dynamic_cast<SQLGeneratorInterfaceImpl*>(grt->get_module("DbMySQL"));
    if (!sqlgenModule)
      throw logic_error("could not find SQL generation module for mysql");
  }
  
  // Build schema_dict by looping over all schemata, add it to the main_dict.
  for (int i= 0; i < (int)catalog->schemata().count(); i++)
  {
    db_mysql_SchemaRef schema= catalog->schemata().get(i);

    TemplateDictionary *schema_dict= main_dict.AddSectionDictionary(REPORT_SCHEMATA);
    schema_dict->SetIntValue(REPORT_SCHEMA_ID, i);
    schema_dict->SetIntValue(REPORT_SCHEMA_NUMBER, i + 1);
    schema_dict->SetValue(REPORT_SCHEMA_NAME, *schema->name());

    set_ddl(schema_dict, sqlgenModule, schema, lexer, show_ddl);

    schema_dict->SetIntValue(REPORT_TABLE_COUNT, (int)schema->tables().count());

    // Loop over all tables. Build the nested tables sub groups and at the same time the
    // full collection of all columns, indices and foreign keys.
    for (int j= 0; j < (int)schema->tables().count(); j++)
    {
      db_mysql_TableRef table= schema->tables().get(j);

      TemplateDictionary *table_dict= schema_dict->AddSectionDictionary(REPORT_TABLES);
      
      // The table id is used as unique id, e.g. in HTML anchors.
      table_dict->SetIntValue(REPORT_TABLE_ID, total_table_count++);
      
      // The table number is used in visible counts like "Table 1 of 20".
      table_dict->SetIntValue(REPORT_TABLE_NUMBER, j + 1);
      
      table_dict->SetValue(REPORT_TABLE_NAME, *table->name());
      table_dict->SetValueAndShowSection(REPORT_TABLE_COMMENT, *table->comment(), REPORT_TABLE_COMMENT_LISTING);

      fillTablePropertyDict(table, table_dict);
      set_ddl(table_dict, sqlgenModule, table, lexer, show_ddl);

      if (columns_show)
      {
        TemplateDictionary *columns_list_dict;
        schema_dict->SetIntValue(REPORT_COLUMN_COUNT, table->columns().count());

        for (int k= 0; k < (int)table->columns().count(); k++)
        {
          // Create the dict for the outer section (inluding header)
          if (k == 0)
            columns_list_dict= table_dict->AddSectionDictionary(REPORT_COLUMNS_LISTING);

          db_mysql_ColumnRef col= table->columns().get(k);

          // Fill data for table details.
          TemplateDictionary *col_dict= columns_list_dict->AddSectionDictionary(REPORT_COLUMNS);
          fillColumnDict(col, table, col_dict, false);

          // Fill data for full details.
          col_dict= schema_dict->AddSectionDictionary(REPORT_COLUMNS);
          fillColumnDict(col, table, col_dict, true);

          col_dict->SetIntValue(REPORT_COLUMN_ID, total_column_count++);
          col_dict->SetIntValue(REPORT_COLUMN_NUMBER, k + 1);
        }
      }

      if (indices_show)
      {
        TemplateDictionary *idx_list_dict;
        schema_dict->SetIntValue(REPORT_INDEX_COUNT, table->indices().count());

        for (int k= 0; k < (int)table->indices().count(); k++)
        {
          // Create the dict for the outer section (inluding header)
          if (k == 0)
            idx_list_dict= table_dict->AddSectionDictionary(REPORT_INDICES_LISTING);

          db_mysql_IndexRef idx= table->indices().get(k);

          TemplateDictionary *idx_dict= idx_list_dict->AddSectionDictionary(REPORT_INDICES);
          fillIndexDict(idx, table, idx_dict, false);

          idx_dict= schema_dict->AddSectionDictionary(REPORT_INDICES);
          fillIndexDict(idx, table, idx_dict, true);

          idx_dict->SetIntValue(REPORT_INDEX_ID, total_index_count++);
          idx_dict->SetIntValue(REPORT_INDEX_NUMBER, k + 1);
        }
      }

      if (fks_show)
      {
        TemplateDictionary *fk_list_dict= NULL;
        schema_dict->SetIntValue(REPORT_FOREIGN_KEY_COUNT, table->foreignKeys().count());

        for (int k= 0; k < (int)table->foreignKeys().count(); k++)
        {
          // Create the dict for the outer section (inluding header)
          if (k == 0)
            fk_list_dict= table_dict->AddSectionDictionary(REPORT_REL_LISTING);

          db_mysql_ForeignKeyRef fk= table->foreignKeys().get(k);

          TemplateDictionary *fk_dict= fk_list_dict->AddSectionDictionary(REPORT_REL);
          fillForeignKeyDict(fk, table, fk_dict, false);

          fk_dict= schema_dict->AddSectionDictionary(REPORT_FOREIGN_KEYS);
          fillForeignKeyDict(fk, table, fk_dict, true);

          fk_dict->SetIntValue(REPORT_FOREIGN_KEY_ID, total_fk_count++);
          fk_dict->SetIntValue(REPORT_FOREIGN_KEY_NUMBER, k + 1);
        }

        if (fks_show_referred_fks)
        {
          map<string, vector<db_mysql_ForeignKeyRef> >::iterator tbl_fk_map_it=
            tbl_fk_map.find(table->id());
          if (tbl_fk_map_it != tbl_fk_map.end())
          {
            vector<db_mysql_ForeignKeyRef>::iterator fk_it= tbl_fk_map_it->second.begin();
            for (; fk_it != tbl_fk_map_it->second.end(); fk_it++)
            {
              if (fk_list_dict == NULL)
                fk_list_dict= table_dict->AddSectionDictionary(REPORT_REL_LISTING);

              db_mysql_ForeignKeyRef fk = *fk_it;

              TemplateDictionary *fk_dict= fk_list_dict->AddSectionDictionary(REPORT_REL);
              fk_dict->SetValue(REPORT_REL_NAME, *fk->name());
              fk_dict->SetValue(REPORT_REL_TYPE, 
                bec::TableHelper::is_identifying_foreign_key(table, fk) ? "Identifying" : "Non-Identifying");
              fk_dict->SetValue(REPORT_REL_PARENTTABLE, *table->name());
              fk_dict->SetValue(REPORT_REL_CHILDTABLE, *fk->owner()->name());
              fk_dict->SetValue(REPORT_REL_CARD, (fk->many() == 1) ? "1:n" : "1:1");
            }
          }
        }
        
        // Triggers.
        schema_dict->SetIntValue(REPORT_TRIGGER_COUNT, table->triggers().count());

        for (int k= 0; k < (int)table->triggers().count(); k++)
        {
          db_mysql_TriggerRef trigger= table->triggers().get(k);
          
          TemplateDictionary *trigger_dict= schema_dict->AddSectionDictionary(REPORT_TRIGGERS);
          fillTriggerDict(trigger, table, trigger_dict);
          set_ddl(trigger_dict, sqlgenModule, trigger, lexer, show_ddl);
          
          trigger_dict->SetIntValue(REPORT_TRIGGER_ID, total_trigger_count++);
          trigger_dict->SetIntValue(REPORT_TRIGGER_NUMBER, k + 1);
        }
      }
    }
    
    // View section.
    schema_dict->SetIntValue(REPORT_VIEW_COUNT, schema->views().count());
    for (int j= 0; j < (int)schema->views().count(); j++)
    {
      db_mysql_ViewRef view= schema->views().get(j);

      TemplateDictionary *view_dict= schema_dict->AddSectionDictionary(REPORT_VIEWS);
      view_dict->SetIntValue(REPORT_VIEW_ID, total_view_count++);
      view_dict->SetIntValue(REPORT_VIEW_NUMBER, j + 1);
      set_ddl(view_dict, sqlgenModule, view, lexer, show_ddl);

      fillViewDict(view, view_dict);
    }

    // Routine section.
    schema_dict->SetIntValue(REPORT_ROUTINE_COUNT, schema->routines().count());
    for (int j= 0; j < (int)schema->routines().count(); j++)
    {
      db_mysql_RoutineRef routine= schema->routines().get(j);
      
      TemplateDictionary *routine_dict= schema_dict->AddSectionDictionary(REPORT_ROUTINES);
      routine_dict->SetIntValue(REPORT_ROUTINE_ID, total_sp_count++);
      routine_dict->SetIntValue(REPORT_ROUTINE_NUMBER, j + 1);
      set_ddl(routine_dict, sqlgenModule, routine, lexer, show_ddl);
      
      fillRoutineDict(routine, routine_dict);
    }
  }

  main_dict.SetIntValue(REPORT_TOTAL_COLUMN_COUNT, total_column_count);
  main_dict.SetIntValue(REPORT_TOTAL_INDEX_COUNT, total_index_count);
  main_dict.SetIntValue(REPORT_TOTAL_FK_COUNT, total_fk_count);
  main_dict.SetIntValue(REPORT_TOTAL_TABLE_COUNT, total_table_count);
  main_dict.SetIntValue(REPORT_TOTAL_VIEW_COUNT, total_view_count);
  main_dict.SetIntValue(REPORT_TOTAL_TRIGGER_COUNT, total_trigger_count);
  main_dict.SetIntValue(REPORT_TOTAL_ROUTINE_COUNT, total_sp_count);

  // --------------------------------------------------------------------------------------------
  // Process template files

  string template_dir= getTemplateDirFromName(template_name);

  // loop over all files in the template dir
  const char *entry;
  GDir *dir= g_dir_open(template_dir.c_str(), 0, NULL);
  if (dir)
  {
    while ((entry= g_dir_read_name(dir)) != NULL)
    {
      char *path= g_build_filename(template_dir.c_str(), entry, NULL);

      // skip the info.xml file and preview pngs
      if (strcmp(entry, "info.xml") == 0 || 
        (g_str_has_prefix(entry, "preview_") && g_str_has_suffix(entry, ".png")))
      {
        g_free(path);
        continue;
      }
      if (g_file_test(path, (GFileTest)(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR)))
      {
        if (g_str_has_suffix(entry, ".tpl"))
        {          
          // load template file
          Template *tpl_index= Template::GetTemplate(path, ctemplate::DO_NOT_STRIP);
          if (tpl_index == 0)
          {
            grt->send_error("Error while loading template files.");
            grt->send_error(path);
            ctemplate::TemplateNamelist::SyntaxListType bad_list= ctemplate::TemplateNamelist::GetBadSyntaxList(true, 
              ctemplate::DO_NOT_STRIP);
            if (bad_list.size() > 0)
            {
              grt->send_error("Syntax errors found in file:");
              for (ctemplate::TemplateNamelist::SyntaxListType::iterator iterator= bad_list.begin(); 
                iterator != bad_list.end(); iterator++)
                  grt->send_error(*iterator);
            }
            grt->make_output_visible();
            g_free(path);
            return 0;
          }

          // expand the template based on the dictionary
          string output;
          tpl_index->Expand(&output, &main_dict);

          // build output file name
          string output_filename;
          
          if (single_file_report)
          {
            // For single file reports the target file name is constructed from the report title.
            output_filename = bec::make_path(output_path, title);
            string template_filename(entry);

            // Remove the .tpl suffix.
            string name = template_filename.substr(0, template_filename.size() - 4);

            // Find the file's target suffix. If there is one use this for the target file too.
            string::size_type p = name.rfind('.');
            if (p != string::npos)
              output_filename += name.substr(p);
          }
          else
          {
            string template_filename(entry);
            output_filename= bec::make_path(output_path, template_filename.substr(0, template_filename.size() - 4));
          }

          // write output to file
          ofstream fs;
          fs.open(output_filename.c_str(), ios::out);
          fs << output;
          fs.close();
        }
        else
        {
          // Copy files/folders.
          string target= bec::make_path(output_path, entry);
          if (g_file_test(path, G_FILE_TEST_IS_DIR))
            copy_folder(path, target.c_str());
          else
            copy_file(path, target.c_str());
        }
      }

      g_free(path);
    }

    g_dir_close(dir);
  }

  if (use_highlighting)
    cleanup_syntax_highlighter();
  
  grt->send_info(strfmt("Schema report written to %s %s", single_file_report ? "file" : "folder", output_path.c_str()));

  return 1;
}


