// subtitleeditor -- a tool to create or edit subtitle
//
// https://kitone.github.io/subtitleeditor/
// https://github.com/kitone/subtitleeditor/
//
// Copyright @ 2005-2018, kitone
//
// 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; either version 3 of the License, or
// (at your option) any later version.
//
// 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, see <http://www.gnu.org/licenses/>.

#include <glibmm.h>
#include "cfg.h"
#include "defaultcfg.h"
#include "utility.h"

namespace cfg {

// FIXME: C++17 variant, optional

using Glib::Error;
using Glib::KeyFileError;
using std::cerr;
using std::endl;
using std::map;

class Configuration {
  typedef signal<void, ustring, ustring> SignalChanged;
  typedef map<ustring, SignalChanged> SignalGroup;

 public:
  Configuration() {
  }

  ~Configuration() {
    save();
  }

  SignalGroup &signals() {
    return m_signals;
  }

  Glib::KeyFile &keyfile() {
    if (!m_keyfile_initialized) {
      load();
    }
    return m_keyfile;
  }

 protected:
  void load() {
    auto file = get_config_dir("config");
    try {
      m_keyfile.load_from_file(file);
    } catch (const Error &ex) {
      cerr << "failed to read config: " << ex.what() << endl;
    }
    load_defaults_on_missing_values();
    m_keyfile_initialized = true;
  }

  void load_defaults_on_missing_values() {
    map<ustring, map<ustring, ustring>> defaults;
    get_default_config(defaults);

    for (const auto &gkv : defaults) {
      auto group = gkv.first;
      auto has_group = m_keyfile.has_group(group);
      // for each key/value of the group
      for (const auto &kv : gkv.second) {
        // if the group or the key doesn't exist, create
        if (!has_group || !m_keyfile.has_key(group, kv.first)) {
          m_keyfile.set_string(group, kv.first, kv.second);
        }
      }
    }
  }

  void save() {
    try {
      auto filename = get_config_dir("config");
      m_keyfile.save_to_file(filename);
    } catch (const Error &ex) {
      cerr << "failed to save the configuration" << ex.what() << endl;
    }
  }

 protected:
  bool m_keyfile_initialized{false};
  Glib::KeyFile m_keyfile;
  SignalGroup m_signals;
};

Configuration &configuration() {
  static Configuration m_config;
  return m_config;
}

Glib::KeyFile &keyfile() {
  return configuration().keyfile();
}

// connect a signal to the group, notify when a key change
sigc::signal<void, ustring, ustring> &signal_changed(const ustring &group) {
  return configuration().signals()[group];
}

// emit a signal on the only to the group
void emit_signal_changed(const ustring &g, const ustring &k, const ustring &v) {
  configuration().signals()[g](k, v);
}

// check if a key exists on the group
bool has_key(const ustring &group, const ustring &key) {
  try {
    return keyfile().has_key(group, key);
  } catch (const KeyFileError &ex) {
    cerr << ex.what() << std::endl;
  }
  return false;
}

// return the keys of the group
vector<ustring> get_keys(const ustring &group) {
  return keyfile().get_keys(group);
}

// check if a group exists
bool has_group(const ustring &group) {
  return keyfile().has_group(group);
}

// remove the group and associated keys
void remove_group(const ustring &group) {
  keyfile().remove_group(group);
}

// set a comment to the key
void set_comment(const ustring &g, const ustring &k, const ustring &v) {
  keyfile().set_comment(g, k, v);
}

// set the string value to the key
void set_string(const ustring &g, const ustring &k, const ustring &v) {
  keyfile().set_string(g, k, v);
  emit_signal_changed(g, k, v);
}

// return a string value of the key
ustring get_string(const ustring &group, const ustring &key) {
  try {
    return keyfile().get_string(group, key);
  } catch (const KeyFileError &ex) {
    cerr << ex.what() << std::endl;
  }
  return ustring();
}

// set the string values to the key
void set_string_list(const ustring &g, const ustring &k,
                     const vector<ustring> &v) {
  keyfile().set_string_list(g, k, v);
  // FIXME: join strings and emit signal
  // emit_signal_changed(g, k, v);
}

// return a strings value of the key
vector<ustring> get_string_list(const ustring &group, const ustring &key) {
  try {
    return keyfile().get_string_list(group, key);
  } catch (const KeyFileError &ex) {
    cerr << ex.what() << std::endl;
  }
  return vector<ustring>();
}

// set the boolean value to the key
void set_boolean(const ustring &g, const ustring &k, const bool &v) {
  keyfile().set_boolean(g, k, v);
  emit_signal_changed(g, k, to_string(v));
}

// return a boolean value of the key
bool get_boolean(const ustring &group, const ustring &key) {
  try {
    return keyfile().get_boolean(group, key);
  } catch (const KeyFileError &ex) {
    cerr << ex.what() << std::endl;
  }
  return false;
}

// set the integer value to the key
void set_int(const ustring &g, const ustring &k, const int &v) {
  keyfile().set_integer(g, k, v);
  emit_signal_changed(g, k, to_string(v));
}

// return a integer value of the key
int get_int(const ustring &group, const ustring &key) {
  try {
    return keyfile().get_integer(group, key);
  } catch (const KeyFileError &ex) {
    cerr << ex.what() << std::endl;
  }
  return 0;
}

// set the double value to the key
void set_double(const ustring &g, const ustring &k, const double &v) {
  keyfile().set_double(g, k, v);
  emit_signal_changed(g, k, to_string(v));
}

// return a double value of the key
double get_double(const ustring &group, const ustring &key) {
  try {
    return keyfile().get_double(group, key);
  } catch (const KeyFileError &ex) {
    cerr << ex.what() << std::endl;
  }
  return 0.0;
}

// FIXME: DELETE ME
float get_float(const ustring &group, const ustring &key) {
  return static_cast<float>(get_double(group, key));
}

ustring get_default(const ustring &group, const ustring &key) {
  map<ustring, map<ustring, ustring>> defaults;
  get_default_config(defaults);
  // find the group
  auto g = defaults.find(group);
  if (g == defaults.end()) {
    return ustring();
  }
  const auto &key_values = g->second;
  // find the key
  auto k = key_values.find(key);
  if (k == key_values.end()) {
    return ustring();
  }
  return k->second;
}

}  // namespace cfg
