/*
   Feather INI Parser - 1.26
   You are free to use this for whatever you wish.

   If you find a bug, please debug the cause and look into a solution.
   Post your compiler, version and the cause or fix in the issues section.

   Written by Turbine.

   Website:
   http://code.google.com/p/feather-ini-parser/downloads

   Help:
   http://code.google.com/p/feather-ini-parser/wiki/Tutorials
*/

#pragma once

#include <map>
#include <string>
#include <fstream>
#include <cstring>
#include <sstream>
#include <stdint.h>
#include <iostream>
#include <stdio.h>
#include <stdlib.h>

///
template<class T, class U>
   static T Convert(U value)
{
   std::stringstream sout;
   T result;

   sout << value;
   sout >> result;

   return result;
}

///
template<class T = std::string, class U = std::string, class V = std::string>
   class INI
{
public:
   typedef char char_t;
   typedef T section_t;
   typedef U key_t;
   typedef V value_t;

   ///Type definitions
   typedef typename std::map<key_t, value_t> keys_t;
   typedef typename std::map<section_t, keys_t*> sections_t;

   typedef typename keys_t::iterator keysit_t;
   typedef typename sections_t::iterator sectionsit_t;

   typedef typename std::pair<key_t, value_t> keyspair_t;
   typedef typename std::pair<section_t, keys_t*> sectionspair_t;

   ///Settings & Tweaks
   static const int BUFFER_SIZE = 512;

   ///Data members
   keys_t* current;
   sections_t sections;

   ///Constuctor/Destructor
   INI(std::string filename, bool parse = true);
   ~INI();

   ///Accessing structure
   keys_t& operator[](section_t section);

   bool Create(const section_t section);
   bool Select(const section_t section);

   //Set
   bool Set(const key_t key, const value_t value);
   template<class W, class X>
      bool Set(const W key, const X value)
         { return Set(Convert<key_t>(key), Convert<value_t>(value)); }

   //Get
   value_t Get(const key_t key, value_t def = value_t());  //Get value, if none exists
   std::string Get(const key_t key, const char_t* def = "");  //Can handle const char* without casting
   template<class W, class X>
      X Get(const W key,  X def = X())
         { return Convert<X>(Get(Convert<value_t>(key), Convert<value_t>(def))); }

   ///Functions
   void Nake(const char_t*);  //Strip the line of any non-interpretable characters
   bool Parse();
   void Clear();

   ///Output
   void Save(std::string filename = "");

private:
   std::string filename;

   ///Output
   std::string makeSection(const section_t& section);
   std::string makeKeyValue(const key_t& key, const value_t& value);

   std::string expandEnv(const char *s);
};

///
template<class T, class U, class V>
   INI<T, U, V>::INI(std::string filename, bool parse): filename(filename), current(NULL)
{
   if (parse)
      Parse();
}

///
template<class T, class U, class V>
   INI<T, U, V>::~INI()
{
   Clear();
}

///
template<class T, class U, class V>
   typename INI<T, U, V>::keys_t& INI<T, U, V>::operator[](section_t section)
{
   return *sections[section];
}

///
template<class T, class U, class V>
   void INI<T, U, V>::Clear()
{
   for(sectionsit_t i = sections.begin(); i != sections.end(); i++)
      delete i->second;

   current = NULL;
}

///
template<class T, class U, class V>
   bool INI<T, U, V>::Create(const section_t section)
{
   if (Select(section))
      return false;

   current = new keys_t;
   sections[section] = current;

   return true;
}

///
template<class T, class U, class V>
   bool INI<T, U, V>::Select(const section_t section)
{
   sectionsit_t sectionsit = sections.find(section);
   if (sectionsit == sections.end())
      return false;

   current = sectionsit->second;

   return true;
}

///
template<class T, class U, class V>
   bool INI<T, U, V>::Set(const key_t key, const value_t value)
{
   if (current == NULL)
      return false;

   (*current)[key] = value;

   return true;
}

///
template<class T, class U, class V>
   typename INI<T, U, V>::value_t INI<T, U, V>::Get(const key_t key, const value_t def)
{
   keysit_t keys = current->find(key);
   if (current == NULL || keys == current->end())
      return def;

   return keys->second;
}

///
template<class T, class U, class V>
   std::string INI<T, U, V>::Get(const key_t key, const char_t* def)
{
   return Get(key, (std::string)def);
}

///
template<class T, class U, class V>
   void INI<T, U, V>::Nake(const char_t*)
{

}

///
template<class T, class U, class V>
   bool INI<T, U, V>::Parse()
{
   current = NULL;
   std::ifstream file(filename.c_str());
   if (!file.is_open())
      return false;

   char_t line[BUFFER_SIZE];

   while(!file.eof())
   {
      file.getline(line, BUFFER_SIZE);

      Nake(line);

      if (line[0] != 0)
      {
         if (!((strlen(line) >= 2) && line[0] == '/' && line[1] == '/'))  //Ignore comment
         {
            if (line[0] == '[')  //Section
            {
               std::stringstream out;
               section_t section;

               size_t length = strlen(line) - 2;  //Without section brackets
               while(isspace(line[length + 1]))  //Leave out any additional new line characters
                  --length;

               char_t* ssection = (char_t*)calloc(sizeof(char_t), length + 1);

               strncpy(ssection, line + 1, length);  //Count after first bracket

               current = new keys_t;

               out << ssection;
               out >> section;

               sections[section] = current;
            }
            else  //Key
            {
               // BSOJ modified //
               // std::stringstream out1, out2;
               // key_t key;
               // value_t value;

               // char_t* skey;
               // char_t* svalue;
               // skey = strtok(line, "=");
               // svalue = strtok(NULL, "=");

               // out1 << skey;
               // out1 >> key;

               // out2 << svalue;
               // out2 >> value;
               char *p = strchr(line, '=');
               if(p == NULL)
                  continue;
               value_t value(expandEnv(p + 1));
               *p = '\0';
               key_t key(line);
               // END //
               //printf("%s -> %s\n", key.c_str(), value.c_str());
               (*current)[key] = value;
            }
         }
      }
   }

   file.close();

   return true;
}

///
template<class T, class U, class V>
   std::string INI<T, U, V>::makeSection(const section_t& section)
{
   std::stringstream line;
   line << '[' << section << ']' << std::endl;

   return line.str();
}

///
template<class T, class U, class V>
   std::string INI<T, U, V>::makeKeyValue(const key_t& key, const value_t& value)
{
   std::stringstream line;
   line << key << '=' << value << std::endl;

   return line.str();
}

///
template<class T, class U, class V>
   void INI<T, U, V>::Save(std::string filename)
{
   std::ofstream file(((filename == "")? this->filename: filename).c_str(), std::ofstream::trunc);

   //Loop through sections
   for(INI::sectionsit_t i = sections.begin(); i != sections.end(); i++)
   {
      if (i->second->size() == 0)  //No keys/values in section, skip to next
         continue;

      //Write section
      const std::string temp = makeSection(i->first);
      const char_t* line = temp.c_str();
      file.write(line, strlen(line));

      for(INI::keysit_t j = i->second->begin(); j != i->second->end(); j++)
      {
         //Write key and value
         const std::string temp = makeKeyValue(j->first, j->second);
         const char* line = temp.c_str();
         file.write(line, strlen(line));
      }
   }

   file.close();
}

template<class T, class U, class V>
   std::string INI<T, U, V>::expandEnv(const char *s)
{
   std::string result;
   const char * start = NULL;
   for(; *s; s++) {
      if(start) {
         if(*s == ')') {
            int length = s-1-start;
            if(length > 0) {
               const char *env = getenv(std::string(start+1, length).c_str());
               if(env)
                  result += env;
               start = NULL;
            }
         }
      }else if(*s == '$' && *(s+1) == '('){
         start = s+1;
      }else {
         result += *s;
      }
   }
   return result;
}
