/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/***************************************************************************
 *            configfile.cc
 *
 *  Thu May 14 14:51:39 CEST 2015
 *  Copyright 2015 Bent Bisballe Nyeng
 *  deva@aasimon.org
 ****************************************************************************/

/*
 *  This file is part of DrumGizmo.
 *
 *  DrumGizmo is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License as published by
 *  the Free Software Foundation; either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  DrumGizmo 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 Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public License
 *  along with DrumGizmo; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA.
 */
#include "configfile.h"

#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

#include <sys/stat.h>
#include <sys/types.h>

#ifdef WIN32
#include <direct.h>
#include <windows.h>
#include <Shlobj.h>
#include <Shlwapi.h>
#else
#endif

#include <hugin.hpp>
  
#ifdef WIN32
  #define SEP "\\"
#else
  #define SEP "/"
#endif

#define CONFIGDIRNAME ".drumgizmo"

/**
 * Return the path containing the config files.
 */
static std::string configPath()
{
#ifdef WIN32
  std::string configpath;
  TCHAR szPath[256];
  if(SUCCEEDED(SHGetFolderPath(NULL, CSIDL_APPDATA | CSIDL_FLAG_CREATE,
                               NULL, 0, szPath))) {
    configpath = szPath;
  }
#else
  std::string configpath = getenv("HOME");
#endif
  configpath += SEP;
  configpath += CONFIGDIRNAME;

  return configpath;
}

/**
 * Calling this makes sure that the config path exists
 */
static bool createConfigPath()
{
  std::string configpath = configPath();

  struct stat st;
  if(stat(configpath.c_str(), &st) == 0) {
    DEBUG(configfile, "No configuration exists, creating directory '%s'\n",
          configpath.c_str());
#ifdef WIN32
    if(mkdir(configpath.c_str()) < 0) { 
#else
    if(mkdir(configpath.c_str(), 0755) < 0) { 
#endif
      DEBUG(configfile, "Could not create config directory\n");
    }

    return false;
  }

  return true;
}

ConfigFile::ConfigFile(std::string filename)
  : filename(filename)
  , fp(NULL)
{
}

ConfigFile::~ConfigFile()
{
}

bool ConfigFile::load()
{
  DEBUG(configfile, "Loading config file...\n");
  if(!open("r")) {
    return false;
  }

  values.clear();

  std::string line;
  while(true) {
    line = readLine();

    if(line == "") break;
    
    if(!parseLine(line)) {
      return false;
    }
  }

  close();

  return true;
}

bool ConfigFile::save()
{
  DEBUG(configfile, "Saving configuration...\n");

  createConfigPath();

  if(!open("w")) {
    return false;
  }

  std::map<std::string, std::string>::iterator v = values.begin();
  for(; v != values.end(); ++v) {
    fprintf(fp, "%s:%s\n", v->first.c_str(), v->second.c_str());
  }

  close();

  return true;
}

std::string ConfigFile::getValue(const std::string& key)
{
  if(values.find(key) != values.end()) {
    return values[key];
  }

  return "";
}

void ConfigFile::setValue(const std::string& key, const std::string& value)
{
  values[key] = value;
}

bool ConfigFile::open(std::string mode)
{
  if(fp) close();

  std::string configpath = configPath();

  std::string configfile = configpath;
  configfile += SEP;
  configfile += filename;

  DEBUG(configfile, "Opening config file '%s'\n", configfile.c_str());
  fp = fopen(configfile.c_str(), mode.c_str());

  if(!fp) return false;

  return true;
}

void ConfigFile::close()
{
  fclose(fp);
  fp = NULL;
}

std::string ConfigFile::readLine()
{
  if(!fp) return "";

  std::string line;

  char buf[1024];
  while(!feof(fp)) {
    char *s = fgets(buf, sizeof(buf), fp);
    if(s) {
      line += buf;
      if(buf[strlen(buf) - 1] == '\n') break;
    }
  }

  return line;
}

bool ConfigFile::parseLine(const std::string& line)
{
  std::string key;
  std::string value;
  enum {
    before_key,
    in_key,
    after_key,
    before_value,
    in_value,
    in_value_single_quoted,
    in_value_double_quoted,
    after_value,
  } state = before_key;

  for(std::size_t p = 0; p < line.size(); ++p) {
    switch(state) {
    case before_key:
      if(line[p] == '#') {
        // Comment: Ignore line.
        p = line.size();
        continue;
      }
      if(std::isspace(line[p])) {
        continue;
      }
      key += line[p];
      state = in_key;
      break;

    case in_key:
      if(std::isspace(line[p])) {
        state = after_key;
        continue;
      }
      if(line[p] == ':' || line[p] == '=') {
        state = before_value;
        continue;
      }
      key += line[p];
      break;

    case after_key:
      if(std::isspace(line[p])) {
        continue;
      }
      if(line[p] == ':' || line[p] == '=') {
        state = before_value;
        continue;
      }
      ERR(configfile, "Bad symbol."
            " Expecting only whitespace or key/value seperator: '%s'",
            line.c_str());
      return false;

    case before_value:
      if(std::isspace(line[p])) {
        continue;
      }
      if(line[p] == '\'') {
        state = in_value_single_quoted;
        continue;
      }
      if(line[p] == '"') {
        state = in_value_double_quoted;
        continue;
      }
      value += line[p];
      state = in_value;
      break;

    case in_value:
      if(std::isspace(line[p])) {
        state = after_value;
        continue;
      }
      if(line[p] == '#') {
        // Comment: Ignore the rest of the line.
        p = line.size();
        state = after_value;
        continue;
      }
      value += line[p];
      break;

    case in_value_single_quoted:
      if(line[p] == '\'') {
        state = after_value;
        continue;
      }
      value += line[p];
      break;

    case in_value_double_quoted:
      if(line[p] == '"') {
        state = after_value;
        continue;
      }
      value += line[p];
      break;

    case after_value:
      if(std::isspace(line[p])) {
        continue;
      }
      if(line[p] == '#') {
        // Comment: Ignore the rest of the line.
        p = line.size();
        continue;
      }
      ERR(configfile, "Bad symbol."
            " Expecting only whitespace or key/value seperator: '%s'",
            line.c_str());
      return false;
    }
  }

  if(state == before_key) {
    // Line did not contain any data (empty or comment)
    return true;
  }

  // If state == in_value_XXX_quoted here, the string was not terminated.
  if(state != after_value && state != in_value) {
    ERR(configfile,"Malformed line: '%s'", line.c_str());
    return false;
  }

  DEBUG(configfile, "key['%s'] value['%s']\n", key.c_str(), value.c_str());

  if(key != "") {
    values[key] = value;
  }

  return true;
}