/*
    EIBD eib bus access and management daemon
    Copyright (C) 2005-2011 Martin Koegler <mkoegler@auto.tuwien.ac.at>

    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 2 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, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

/**
 * This
 */

#include <iostream>
#include <fstream>

#include <cassert>
#include <cstring>

#include "inifile.h"
#include "types.h"

IniSection::IniSection(IniData &p, const std::string& n, bool autogenerated)
  : values(), parent(p), name(n)
{
  this->autogenerated = autogenerated;
}
IniSection::IniSection(IniData &p, const std::string&& n, bool autogenerated)
  : values(), parent(p), name(n)
{
  this->autogenerated = autogenerated;
}

const std::string&
IniSection::value(const std::string& name, const std::string& def)
{
  auto v = values.find(name);
  if (v == values.end())
    {
      v = values.find("use");
      if (v == values.end())
        return def;
      return parent[v->second.first]->value(name,def);
    }

  v->second.second = true;
  return v->second.first;
}

const std::string
IniSection::value(const std::string& name, const char *def)
{
  std::string s = def;
  return value(name,s);
}

static const std::string empty = "";

std::string&
IniSection::operator[](const char *name)
{
  if (!autogenerated && parent.read_only)
    {
      auto v = values.find(name);
      assert (v != values.end());
      return v->second.first;
    }
  else
    {
      auto res = values.emplace(name, ValueType("",false));
      return res.first->second.first;
    }
}

int
IniSection::value(const std::string& name, int def)
{
  static const std::string empty = "";
  const std::string v { value(name, empty) };
  if (!v.size())
    return def;
  size_t pos;
  int res = std::stoi(v, &pos, 0);
  if (pos == v.size())
    return res;
  std::cerr << "Parse error: Not an integer: " << name << "=" << v << std::endl;
  return def;
}

double
IniSection::value(const std::string& name, double def)
{
  static const std::string empty = "";
  const std::string v { value(name, empty) };
  if (!v.size())
    return def;
  char *pos;
  int res = std::strtod(v.c_str(), &pos);
  if (!*pos)
    return res;
  std::cerr << "Parse error: Not a float: " << name << "=" << v << std::endl;
  return def;
}

bool
IniSection::value(const std::string& name, bool def)
{
  static const std::string empty = "";
  const std::string& v { value(name, empty) };
  if (!v.size())
    return def;
  if (!v.compare("Y"))
    return true;
  if (!v.compare("N"))
    return false;
  if (!v.compare("y"))
    return true;
  if (!v.compare("n"))
    return false;
  if (!v.compare("1"))
    return true;
  if (!v.compare("0"))
    return false;
  if (!v.compare("true"))
    return true;
  if (!v.compare("false"))
    return false;
  if (!v.compare("True"))
    return true;
  if (!v.compare("False"))
    return false;
  if (!v.compare("TRUE"))
    return true;
  if (!v.compare("FALSE"))
    return false;

  std::cerr << "Parse error: Not a bool: " << name << "=" << v << std::endl;
  return def;
};

IniData::IniData() : sections() { }

static int
inidata_handler(void* user, const char* section, const char* name, const char* value)
{
  IniData *data = (IniData *)user;
  return data->add(section, name, value);
}

bool
IniData::add(const char *section, const char *name, const char *value)
{
  auto sec = IniSectionPtr(new IniSection(*this,section));
  auto res = sections.emplace(section, SectionType(sec,false));
  if (! res.second && name == NULL)
    {
      std::cerr << "Parse error: Duplicate section: " << section << std::endl;
      return false;
    }

  IniSectionPtr& s = res.first->second.first;
  if (name == NULL)
    return true;
  return s->add(name,value);
}

IniSectionPtr
IniData::add_auto(std::string& section)
{
  return IniSectionPtr(new IniSection(*this, section + '_', true));
}

IniSectionPtr&
IniData::operator[](const char *name)
{
  auto v = sections.find(name);
  if (v == sections.end())
    {
      auto sec = IniSectionPtr(new IniSection(*this,name));
      auto res = sections.emplace(name, SectionType(sec,false));
      v = res.first;
    }
  v->second.second = true;
  return v->second.first;
}

IniSectionPtr
IniSection::sub(const char *name, bool force)
{
  std::string n = value(name,"");
  if (force && !n.size())
    return shared_from_this();
  return parent[n];
}

bool
IniSection::add(const char *name, const char *value)
{
  if (value == NULL)
    value = "TRUE"; // assume bool flag

  auto res2 = values.emplace(name, ValueType(value,false));
  if (! res2.second)
    {
      std::cerr << "Parse error: Duplicate value: " << name << std::endl;
      return false;
    }
  return true;
}

static char*
inidata_reader(char* str, int num, void* stream)
{
  std::istream *f = (std::istream *)stream;

  if (f->rdstate() & std::ifstream::eofbit)
    return NULL;
  f->getline(str, num);
  if (f->rdstate() & std::ifstream::failbit)
    return NULL;
  return str;
}

int
IniData::parse(const std::string& filename)
{
  std::filebuf fb;
  if (filename == "-")
    {
      return parse(std::cin);
    }
  else if (fb.open (filename,std::ios::in))
    {
      std::istream s(&fb);
      return parse(s);
    }
  else
    {
      std::cerr << "Unable to open: " << filename << ": " << strerror(errno) << std::endl;
      return 0;
    }
}

int
IniData::parse(std::istream& file)
{
  int res = ini_parse_stream(&inidata_reader, (void *)&file,
                             &inidata_handler, (void *)this);
  read_only = true;
  return res;
}

void
IniSection::write(std::ostream& file)
{
  ITER(i,values)
  file << i->first << " = " << i->second.first << std::endl;
}

void
IniData::write(std::ostream& file)
{
  ITER(i,sections)
  {
    file << '[' << i->first << ']' << std::endl;
    i->second.first->write(file);
  }
}

bool
IniSection::list_unseen(UnseenViewer uv, void *x)
{
  bool res = false;
  ITER(i,values)
  {
    if (!i->second.second)
      {
        if (uv(x, *this, i->first, i->second.first))
          res = true;
      }
  }
  return res;
}

bool
IniData::list_unseen(UnseenViewer uv, void *x)
{
  bool res = false;
  ITER(i,sections)
  {
    if (!i->second.second)
      continue;
    if (i->second.first->list_unseen(uv, x))
      res = true;
  }
  return res;
}

