/* ====================================================================
 * Copyright (c) 2003-2009  Martin Hauner
 *                          http://subcommander.tigris.org
 *
 * Subcommander is licensed as described in the file doc/COPYING, which
 * you should have received as part of this distribution.
 * ====================================================================
 */

//sc
#include "ConfigManager.h"
#include "Project.h"
#include "Repository.h"
#include "ColorId.h"
#include "config.cpp"
#include "settings/ProjectReader.h"
#include "settings/ProjectWriter.h"
#include "sublib/ColorStorage.h"
#include "sublib/Utility.h"
#include "sublib/settings/LayoutSettingsHandler.h"
#include "sublib/config/ConfigFile.h"
#include "sublib/config/ConfigData.h"

// qt
#include <QtCore/QString>
#include <QtCore/QRegExp>
#include <QtCore/QDir>
#include <QtCore/QTextStream>
#include <QtCore/QDateTime>
#include <QtGui/QApplication>
#include <Qt3Support/Q3Header>

///////////////////////////////////////////////////////////////////////////////

// subversion strings
static sc::String SvnAspDotNetHack("subversion.asp_dot_net_hack");

// < 0.15, obsolete
static sc::String OptForceId("option.force");
static sc::String OptRecursiveId("option.recursive");
static sc::String OptWcUpdatesId("option.wc.updates");
static sc::String OptWcIgnoredId("option.wc.ignored");
static sc::String OptWcAllId("option.wc.all");

// < 1.3, obsolete
static sc::String OptCmdForceId("option.command.force");
static sc::String OptCmdRecursiveId("option.command.recursive");
static sc::String OptStatusUpdatesId("option.status.updates");
static sc::String OptStatusIgnoredId("option.status.ignored");
static sc::String OptStatusRecursiveId("option.status.recursive");

///////////////////////////////////////////////////////////////////////////////


ConfigManager::ConfigManager()
: _dbgDump(false), _dbgLog(false)
{
  _file = new ConfigFile( sc::String("sc.ini"), defaultConf, sizeof(defaultConf) );
  _cfg = new ConfigData( _file );
}

ConfigManager::~ConfigManager()
{
  delete _cfg;
  delete _file;
}

void ConfigManager::load()
{
  _cfg->load();

  // init colors
  Colors colors;
  getColors(colors);
  ColorStorage::setColors(colors);
}

void ConfigManager::save()
{
  clean();
  _cfg->save();
}

// Settings
void ConfigManager::getProjects( Projects& projects )
{
  ProjectReader reader(_cfg);
  reader.readProjects();
  reader.getProjects(projects);
}

void ConfigManager::setProject( const Project* prj )
{
  ProjectWriter writer(_cfg,prj);
  writer.write();
}

void ConfigManager::removeProject( const Project* prj )
{
  QString key = QString("project.%1.").arg( prj->getIndex() );
  _cfg->delValues( sc::String(key) );
}

QFont ConfigManager::getGeneralFont()
{
  ConfigValue fontString = _cfg->getValue( sc::String("font.general") );

  if( fontString.isNull() )
  {
    return qApp->font();
  }

  QFont font;
  font.fromString( (const char*)fontString.getStringValue() );
  return font;
}

void ConfigManager::setGeneralFont( const QFont& font )
{
  if( QApplication::font() != font )
  {
    ConfigValues values;
    values.push_back( ConfigValue(sc::String("font.general"),sc::String(font.toString())) );
    _cfg->setValues( sc::String("font.general"), values );

    QApplication::setFont( font, true );
  }
}

QFont ConfigManager::getEditorFont()
{
  ConfigValue fontString = _cfg->getValue( sc::String("font.editor") );

  if( fontString.isNull() )
  {
#if _WIN32
    return QFont( "Courier New" , 9, QFont::Normal, false );
#elif _MACOSX
    return QFont( "Andale Mono" , 12, QFont::Normal, false );
#else // Unix/Linux
    return QFont( "Courier New" , 9, QFont::Normal, false );
#endif
  }

  QFont font;
  font.fromString( (const char*)fontString.getStringValue() );
  return font;
}

void ConfigManager::setEditorFont( const QFont& font )
{
  ConfigValues values;
  values.push_back( ConfigValue(sc::String("font.editor"),sc::String(font.toString())) );
  _cfg->setValues( sc::String("font.editor"), values );

  //doesn't work as expected, this will change the font of any _new_ QTextEdit
  //to the given font but not the currently open ones.
  //QApplication::setFont( font, true, "QTextEdit" );
}

Color ConfigManager::getColor( long id, const sc::String& name, const QColor& def,
  const sc::String& desc )
{
  ConfigValue color = _cfg->getValue( name );
  if( color.isNull() )
  {
    return Color( id, (const char*)name, def, (const char*)desc );
  }

  return Color( id, QString::fromUtf8(color.getKey()),QColor(color.getStringValue()),
    (const char*)desc );
}

void ConfigManager::getColors( Colors& colors )
{
  // misc colors
  colors.push_back( getColor( ColorFaded,       sc::String("color.faded"),      QColor(150,150,150),
    sc::String("-") ) );

  // status colors
  colors.push_back( getColor( ColorNone,        sc::String("color.none"),        QColor(230,230,230),
    sc::String("-") ) );

  colors.push_back( getColor( ColorUnversioned, sc::String("color.unversioned"), QColor(240,240,240),
    sc::String("-") ) );

  colors.push_back( getColor( ColorNormal,      sc::String("color.normal"),      QColor(255,255,255),
    sc::String("-") ) );

  colors.push_back( getColor( ColorAdded,       sc::String("color.added"),       QColor(200,200,230),
    sc::String("-") ) );

  colors.push_back( getColor( ColorMissing,     sc::String("color.missing"),     QColor(230,230,230),
    sc::String("-") ) );

  colors.push_back( getColor( ColorDeleted,     sc::String("color.deleted"),     QColor(230,200,200),
    sc::String("-") ) );

  colors.push_back( getColor( ColorReplaced,    sc::String("color.replaced"),    QColor(230,230,230),
    sc::String("-") ) );

  colors.push_back( getColor( ColorModified,    sc::String("color.modified"),    QColor(200,230,200),
    sc::String("-") ) );

  colors.push_back( getColor( ColorMerged,      sc::String("color.merged"),      QColor(230,230,000),
    sc::String("-") ) );

  colors.push_back( getColor( ColorConflicted,  sc::String("color.conflicted"),  QColor(240,200,200),
    sc::String("-") ) );

  colors.push_back( getColor( ColorIgnored,     sc::String("color.ignored"),     QColor(230,230,230),
    sc::String("-") ) );

  colors.push_back( getColor( ColorObstructed,  sc::String("color.obstructed"),  QColor(230,230,230),
    sc::String("-") ) );

  colors.push_back( getColor( ColorExternal,    sc::String("color.external"),    QColor(230,230,230),
    sc::String("-") ) );
  
  colors.push_back( getColor( ColorIncomplete,  sc::String("color.incomplete"),  QColor(230,230,230),
    sc::String("-") ) );
  
  colors.push_back( getColor( ColorFallback,    sc::String("color.fallback"),    QColor(230,230,230),
    sc::String("-") ) );
}

void ConfigManager::setColors( const Colors& colors )
{
  ConfigValues values;

  for( Colors::const_iterator it = colors.begin(); it != colors.end(); it++ )
  {
    const Color& c = *it;
    values.push_back( 
      ConfigValue( sc::String(c._name.utf8()),sc::String(c._color.name())) );
  }

  _cfg->setValues( sc::String("color."), values );

  ColorStorage::setColors(colors);

  // refresh the whole app
  qApp->mainWidget()->hide();
  qApp->mainWidget()->show();
}

int ConfigManager::getValue( const QString& key, int def )
{
  return LayoutSettingsHandler::getValue(
    _cfg, QString("layout.%1.value").arg(key), def );
}

void ConfigManager::setValue( const QString& key, int pos )
{
  LayoutSettingsHandler::setValue(
    _cfg, QString("layout.%1.value").arg(key), pos );
}

QSize ConfigManager::getSize( const QString& key, const QSize& def )
{
  return LayoutSettingsHandler::getSize(
    _cfg, QString("layout.%1.size").arg(key), def );
}

void ConfigManager::setSize( const QString& key, const QSize& size )
{
  LayoutSettingsHandler::setSize(
    _cfg, QString("layout.%1.size").arg(key), size );
}

QRect ConfigManager::getRect( const QString& key, const QRect& def )
{
  return LayoutSettingsHandler::getRect(
    _cfg, QString("layout.%1.rect").arg(key), def );
}

void ConfigManager::setRect( const QString& key, const QRect& rect )
{
  LayoutSettingsHandler::setRect(
    _cfg, QString("layout.%1.rect").arg(key), rect );
}

QByteArray ConfigManager::getByteArray( const QString& key, const QByteArray& def )
{
  return LayoutSettingsHandler::getByteArray(
    _cfg, QString("layout.%1").arg(key), def );
}

void ConfigManager::setByteArray( const QString& key, const QByteArray& bytearray )
{
  LayoutSettingsHandler::setByteArray(
    _cfg, QString("layout.%1").arg(key), bytearray );
}

void ConfigManager::getHeaderColumns( const QString& key, Q3Header* header )
{
  QString     qkey  = QString("layout.header.%1").arg(key);
  ConfigValue value = _cfg->getValue( sc::String(qkey) );

  if( value.isNull() )
  {
    return;
  }
  
  QString qval = (const char*)value.getStringValue();

  int cnt = 0;
  while(true)
  {    
    QString section = qval.section( ',', cnt );

    if( section.isEmpty() )
      break;

    QRegExp exp("(\\d+):(\\d+)");
    exp.exactMatch(section);
    
    header->moveSection( exp.cap(1).toInt(), cnt );
    header->resizeSection( exp.cap(1).toInt(), exp.cap(2).toInt() );
    cnt++;
  }
}

void ConfigManager::setHeaderColumns( const QString& key, const Q3Header* header )
{
  QString qkey = QString("layout.header.%1").arg(key);
  QString sections;
  
  for( int i = 0; i < header->count(); i++ )
  {
    QString v;
    int s = header->mapToSection(i);
    v.setNum(s);
    sections += v;
    sections += ":";    
    v.setNum( header->sectionSize(s) );
    sections += v;
    sections += ",";    
  }
  // strip trailing ","
  sections.truncate( sections.length()-1 );

  sc::String valkey = sc::String(qkey);
  ConfigValue value( valkey, sc::String(sections) );
  _cfg->setValue( value.getKey(), value );
}

sc::String ConfigManager::getBuiltinDiffCmd()
{
  QString    path = getMergePath();
  sc::String diff = sc::String(path.utf8());

  diff += " diff -L {llabel} -L {rlabel} {left} {right}";

  return diff;
}

sc::String ConfigManager::getDiffCmd()
{
  ConfigValue option = _cfg->getValue( sc::String("commands.diff") );

  if( option.isNull() )
  {
    return getBuiltinDiffCmd();
  }

  sc::String val = option.getStringValue();
  return val;
}

void ConfigManager::setDiffCmd( const sc::String& cmd )
{
  ConfigValue value(sc::String("commands.diff"),cmd);

  ConfigValues values;
  values.push_back( value );
  _cfg->setValues( value.getKey(), values );
}

sc::String ConfigManager::getBuiltinMergeCmd()
{
  QString    path  = getMergePath();
  sc::String merge = sc::String(path.utf8());

  merge += " merge {base} {mine} {theirs} {merged}";

  return merge;
}

sc::String ConfigManager::getMergeCmd()
{
  ConfigValue option = _cfg->getValue( sc::String("commands.merge") );

  if( option.isNull() )
  {
    return getBuiltinMergeCmd();
  }

  sc::String val = option.getStringValue();
  return val;
}

void ConfigManager::setMergeCmd( const sc::String& cmd )
{
  ConfigValue value(sc::String("commands.merge"),cmd);

  ConfigValues values;
  values.push_back( value );
  _cfg->setValues( value.getKey(), values );
}

void ConfigManager::getLogs( Logs& logs )
{
  sc::String path = _file->getConfigPath();
  QString qpath = QString::fromUtf8(path);

  QDir commit( qpath + "/CommitLogHistory" );
  if( ! commit.exists() )
  {
    commit.mkdir( commit.absPath() );
  }

  commit.setSorting( QDir::Time | QDir::Reversed );
  commit.setFilter( QDir::Files );

  QFileInfoList list = commit.entryInfoList();
  for( QFileInfoListIterator it = list.begin(); it != list.end(); it++ )
  {
    QFile file( (*it).absFilePath() );
    QTextStream stream( &file );
    stream.setEncoding( QTextStream::UnicodeUTF8 );

    if( file.open(IO_ReadOnly) )
    {
      QString    qlog = stream.read();
      sc::String log( qlog.utf8() );

      logs.push_back(log);

      file.close();
    }
  }
}

void ConfigManager::addLog( const sc::String& log )
{
  sc::String path = _file->getConfigPath();
  QString qpath = QString::fromUtf8(path);

  QDir commit( qpath + "/CommitLogHistory" );
  commit.setSorting( QDir::Time | QDir::Reversed );
  commit.setFilter( QDir::Files );


  // if we already have the same message delete the older one
  QFileInfoList list = commit.entryInfoList();
  for( QFileInfoListIterator it = list.begin(); it != list.end(); it++ )
  {
    QFile file( (*it).absFilePath() );
    QTextStream stream( &file );
    stream.setEncoding( QTextStream::UnicodeUTF8 );

    if( file.open(IO_ReadOnly) )
    {
      QString    qlog = stream.read();
      sc::String olog( qlog.utf8() );
      file.close();

      if( log == olog )
      {
        QFile file( (*it).absFilePath() );
        file.remove();

        commit.refresh();
        break;
      }
    }
  }


  QDateTime time = QDateTime::currentDateTime();

  QFile file( commit.absPath() + "/" + time.toString("yyyyMMddhhmmss") );

  if( file.open(IO_WriteOnly) )
  {
    QTextStream stream( &file );
    stream.setEncoding( QTextStream::UnicodeUTF8 );
    stream << QString::fromUtf8(log);
    file.close();
  }

  // if we have more than x logs, delete the oldest log.
  QFileInfoList listd = commit.entryInfoList();

  if( listd.count() > 30 )
  {
    QFile file( (*listd.begin()).absFilePath() );
    file.remove();
  }
}

bool ConfigManager::getAspDotNetHack()
{
  ConfigValue option = _cfg->getValue( SvnAspDotNetHack );

  if( option.isNull() )
  {
    return false;
  }

  return option.getBoolValue();
}

void ConfigManager::setAspDotNetHack( bool enable )
{
  ConfigValue value(SvnAspDotNetHack,enable);

  ConfigValues values;
  values.push_back( value );
  _cfg->setValues( value.getKey(), values );
}

bool ConfigManager::getDump()
{
  return _dbgDump;
}

void ConfigManager::setDump(bool b)
{
  _dbgDump = b;
  setDumpOnException(b);
}

bool ConfigManager::getLog()
{
  return _dbgLog;
}

void ConfigManager::setLog(bool b)
{
  _dbgLog = b;
}

bool ConfigManager::getL10n()
{
  ConfigValue option = _cfg->getValue(DebugL10n);

  if( option.isNull() )
  {
    return true;
  }

  return option.getBoolValue();
}

void ConfigManager::setL10n(bool b)
{
  ConfigValue value(DebugL10n,b);

  ConfigValues values;
  values.push_back( value );
  _cfg->setValues( value.getKey(), values );
}

bool ConfigManager::getOptCommandForce() const
{
  ConfigValue option = _cfg->getValue( OptCmdForceId );

  if( option.isNull() )
  {
    // < 0.15 option available?
    option = _cfg->getValue( OptForceId );
  }

  if( option.isNull() )
  {
    return false;
  }

  return option.getBoolValue();
}

void ConfigManager::setOptCommandForce( bool b )
{
  ConfigValue value(OptCmdForceId,b);

  ConfigValues values;
  values.push_back( value );
  _cfg->setValues( value.getKey(), values );
}

bool ConfigManager::getOptCommandRecursive() const
{
  ConfigValue option = _cfg->getValue( OptCmdRecursiveId );

  if( option.isNull() )
  {
    return true;
  }

  return option.getBoolValue();
}

void ConfigManager::setOptCommandRecursive( bool b )
{
  ConfigValue value(OptCmdRecursiveId,b);

  ConfigValues values;
  values.push_back( value );
  _cfg->setValues( value.getKey(), values );
}

bool ConfigManager::getOptStatusUpdates() const
{
  ConfigValue option = _cfg->getValue( OptStatusUpdatesId );

  if( option.isNull() )
  {
    // < 0.15 option available?
    option = _cfg->getValue( OptWcUpdatesId );
  }

  if( option.isNull() )
  {
    return false;
  }

  return option.getBoolValue();
}

void ConfigManager::setOptStatusUpdates( bool b )
{
  ConfigValue value(OptStatusUpdatesId,b);

  ConfigValues values;
  values.push_back( value );
  _cfg->setValues( value.getKey(), values );
}

bool ConfigManager::getOptStatusIgnored() const
{
  ConfigValue option = _cfg->getValue( OptStatusIgnoredId );

  if( option.isNull() )
  {
    // < 0.15 option available?
    option = _cfg->getValue( OptWcIgnoredId );
  }

  if( option.isNull() )
  {
    return false;
  }

  return option.getBoolValue();
}

void ConfigManager::setOptStatusIgnored( bool b )
{
  ConfigValue value(OptStatusIgnoredId,b);

  ConfigValues values;
  values.push_back( value );
  _cfg->setValues( value.getKey(), values );
}

bool ConfigManager::getOptStatusRecursive() const
{
  ConfigValue option = _cfg->getValue( OptStatusRecursiveId );

  if( option.isNull() )
  {
    // < 0.15 option available?
    option = _cfg->getValue( OptRecursiveId );
  }

  if( option.isNull() )
  {
    return true;
  }

  return option.getBoolValue();
}

void ConfigManager::setOptStatusRecursive( bool b )
{
  ConfigValue value(OptStatusRecursiveId,b);

  ConfigValues values;
  values.push_back( value );
  _cfg->setValues( value.getKey(), values );
}

void ConfigManager::clean()
{
  // obsolete since 0.15
  _cfg->delValues( OptForceId );
  _cfg->delValues( OptWcAllId );
  _cfg->delValues( OptWcUpdatesId );
  _cfg->delValues( OptWcIgnoredId );
  _cfg->delValues( OptRecursiveId );

  // obsolete since 2.0.0
  _cfg->delValues( sc::String("option.status.all") );
  _cfg->delValues( sc::String("layout.MainWindow") );
  _cfg->delValues( sc::String("layout.ListWidget.split") );
  _cfg->delValues( sc::String("layout.ProjectFolderView") );
  _cfg->delValues( sc::String("layout.RepositoryFilesView") );
  _cfg->delValues( sc::String("layout.WorkingCopyFilesView") );
  _cfg->delValues( sc::String("layout.position") );

  // obsolete since 2.0.0 b5
  _cfg->delValues( sc::String("layout.main.maximized.value") );
  _cfg->delValues( sc::String("layout.main.rect") );
  _cfg->delValues( sc::String("layout.main.shpos.value") );
  _cfg->delValues( sc::String("layout.main.svpos.value") );

  // remove some other wrong settings
  _cfg->delValues( sc::String("layout.unnamed") );
  _cfg->delValues( sc::String("layout.header.unnamed") );
  _cfg->delValues( sc::String("layout.geometry.unnamed") );
  _cfg->delValues( sc::String("layout.position.unnamed") );
  _cfg->delValues( sc::String("layout.RepositoryDialog") );
  _cfg->delValues( sc::String("layout.position.ListWidget.split") );
}
