/* ====================================================================
 * Copyright (c) 2003-2007, 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 "ConfigFile.h"
#include "util/apr.h"
#include "util/AprException.h"

// apr
#include <apr_env.h>
#include <apr_strings.h>
#include <apr_file_io.h>
#include <apr_file_info.h>

// qt
#include <QtCore/QRegExp>
#include <QtCore/QString>

// sys
#include <fstream>


const int align = -30;  // left align config value keys in a character field of this size.


ConfigFile::ConfigFile( const sc::String& name, const unsigned char* cfgTemplate, int cfgSize,
  const sc::String& path ) : _cfgTemplate(cfgTemplate), _cfgSize(cfgSize)
{
  _name = name;

  if( path == ConfigPathSys )
  {
    _path = getSystemConfigPath();
  }
  else
  {
    _path = path;
  }
}

#define MAXCFGLINESIZE 1024

void ConfigFile::get( ConfigValues& cfg )
{
  ensureConfig();

  std::string ini;
  ini = _path.getStr();
  ini += "/";
  ini += _name.getStr();

  std::ifstream in( ini.c_str(), std::ios::binary );
  if( !in )
  {
    // error
  }

  while( ! in.eof() )
  {
    char buf[MAXCFGLINESIZE];

    // read line without the endline.
    in.get( buf, MAXCFGLINESIZE );

    if( in.eof() )
    {
      break;
    }

    // reset state after an empty line.
    if( in.fail() )
    {
      in.clear();
    }

    // skip endline.
    in.ignore();

    insertConfigValue( sc::String(buf), cfg );
  }
}

void ConfigFile::set( const ConfigValues& cfg )
{
  std::string ini;
  ini = _path.getStr();
  ini += "/";
  ini += _name.getStr();

  std::string initmp(ini);
  initmp += ".tmp";

  std::ofstream out( initmp.c_str(), std::ios::binary );

  // write remembered header
  for( THeader::iterator it = _header.begin(); it != _header.end(); it++ )
  {
    const sc::String& s = *it;

    if( (const char*)s )
    {
      out << s;
    }
    out << '\n';
  }
  out << '\n';

  // write config values
  for( ConfigValues::const_iterator it = cfg.begin(); it != cfg.end(); it++ )
  {
    const ConfigValue& value = *it;

    QString line = QString("%1 = %2").arg( QString::fromUtf8(value.getKey()), align ).
      arg( QString::fromUtf8((value.getStringValue())) );

    const sc::String& s = sc::String(line.toUtf8());

    if( (const char*)s )
    {
      out << s;
    }
    out << '\n';
  }
  out.close();

  apr::Pool    pool;  
  apr_status_t status;
  status = apr_file_remove( ini.c_str(), pool );
  status = apr_file_rename( initmp.c_str(), ini.c_str(), pool );
}

// old, ie. 0.6.0, 0.7.0
static QRegExp reProjectSection( "^\\[Project\\]\\s*$" );
static QRegExp reComment( "^#(?!#).*$" );

// new
static QRegExp reReadKeyValue( "^(\\S+)\\s+=\\s+(.*)\\s*$" );
static QRegExp reHeader( "^##.*$" );

void ConfigFile::insertConfigValue( const sc::String& s, ConfigValues& values )
{
  static int prjcnt = -1;

  if( reProjectSection.exactMatch(QString::fromUtf8(s)) )
  {
    prjcnt++;
    return;
  }

  if( reComment.exactMatch(QString::fromUtf8(s)) )
  {
    // drop old simple comments.
  }
  else if( reHeader.exactMatch(QString::fromUtf8(s)) )
  {
    // remember header comments.
    _header.push_back(s);
  }
  else if( reReadKeyValue.exactMatch(QString::fromUtf8(s)) )
  {
    QString key = reReadKeyValue.cap(1);
    QString val = reReadKeyValue.cap(2);

    // convert old keys to new keys:
    if( key == "name" )
    {
      key = QString( "project.%1.name" ).arg( prjcnt );
    }
    else if( key == "guid" )
    {
      key = QString( "project.%1.guid" ).arg( prjcnt );
    }
    else if( key == "trunk-name" )
    {
      key = QString( "project.%1.trunk.name" ).arg( prjcnt );
    }
    else if( key == "trunk-url" )
    {
      key = QString( "project.%1.trunk.url" ).arg( prjcnt );
    }
    else if( key == "branches-name" )
    {
      key = QString( "project.%1.branches.name" ).arg( prjcnt );
    }
    else if( key == "branches-url" )
    {
      key = QString( "project.%1.branches.url" ).arg( prjcnt );
    }
    else if( key == "tags-name" )
    {
      key = QString( "project.%1.tags.name" ).arg( prjcnt );
    }
    else if( key == "tags-url" )
    {
      key = QString( "project.%1.tags.url" ).arg( prjcnt );
    }
    else if( key == "wc-name" )
    {
      key = QString( "project.%1.wc.0.name" ).arg( prjcnt );

      QString keyc = QString( "project.%1.wc.current" ).arg( prjcnt );
      values.push_back( ConfigValue(sc::String(keyc.toUtf8()),sc::String("0")) );
    }
    else if( key == "wc-path" )
    {
      key = QString( "project.%1.wc.0.path" ).arg( prjcnt );
    }

    // store key
    values.push_back( ConfigValue( sc::String(key.toUtf8()), sc::String(val.toUtf8()) ) );
  }
}

void ConfigFile::ensureConfig()
{
  apr::Pool    pool;
  apr_status_t status  = 0;
  apr_finfo_t  finfo;

  // check configuration directory
  status = apr_stat( &finfo, _path.getStr(), APR_FINFO_TYPE, pool );

  if( status != APR_SUCCESS )
  {
    status = apr_dir_make( _path.getStr(), APR_OS_DEFAULT, pool );

    if( status != APR_SUCCESS )
    {
      sc::String msg( "failed to create configuration folder: " );
      msg += _path;
      throw apr::Exception( status, msg );
    }
  }

  char* cfile = apr_psprintf( pool, "%s/%s", _path.getStr(), _name.getStr() );
  status      = apr_stat( &finfo, cfile, APR_FINFO_TYPE, pool );

  if( status != APR_SUCCESS )
  {
    apr_file_t* file;
    status = apr_file_open( &file, cfile, APR_CREATE|APR_WRITE, APR_OS_DEFAULT, pool );
    apr_size_t  size = _cfgSize;
    status = apr_file_write( file, _cfgTemplate, &size );
    status = apr_file_close( file );
  }
}

const sc::String& ConfigFile::getConfigPath() const
{
  return _path;
}

#ifdef _WIN32
// get HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders\AppData
bool getSystemConfigPathFromRegistry( char** appdata, apr_pool_t* pool )
{
  bool result = false;

  LONG res;
  HKEY hShellFolders;
  char regAppdata[MAX_PATH] = {};
  DWORD regAppdataSize = sizeof(regAppdata);

  res = RegOpenKeyEx( HKEY_CURRENT_USER,
    "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders", 0,
    KEY_READ, &hShellFolders );

  if( res == ERROR_SUCCESS )
  {
    result = RegQueryValueEx( hShellFolders, "AppData", NULL, NULL, (LPBYTE)
      &regAppdata, &regAppdataSize );

    if( res == ERROR_SUCCESS )
    {
      *appdata = apr_psprintf( pool, "%s", regAppdata );
      result = true;
    }

    RegCloseKey(hShellFolders);
  }

  return result;
}
#endif // _WIN32

sc::String ConfigFile::getSystemConfigPath()
{
  apr::Pool pool;
  char* confpath = 0;

#ifdef _WIN32

  char* appdata = 0;
  apr_status_t status = apr_env_get( &appdata, "APPDATA", pool );
  if( status != APR_SUCCESS )
  {
    // if APPDATA is not set try the registry
    if( !getSystemConfigPathFromRegistry(&appdata,pool) )
    {
      // failed => crash
      return sc::String();
    }
  }

  confpath = apr_psprintf( pool, "%s/Subcommander", appdata );

#else // ! _WIN32

  apr_status_t status;
  apr_uid_t uid;
  apr_gid_t gid;
  char* username = 0;
  char* homepath = 0;

  status = apr_uid_current( &uid, &gid, pool );
  status = apr_uid_name_get( &username, uid, pool );
  status = apr_uid_homepath_get( &homepath, username, pool );
  
  confpath = apr_psprintf( pool, "%s/%s", homepath, ".subcommander" );

#endif 

  return sc::String(confpath);
}
