OZ++ Class: ConfigParser
/******************************************************************************
 *
 * Copyright (c) 2019 Antillia.com TOSHIYUKI ARAI. ALL RIGHTS RESERVED.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions, and the following disclaimer.
 *
 * 2. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *
 *  ConfigParser.h
 *
 *****************************************************************************/

// 2019/06/04 
// This class will parse  an ini config file, and store the information
// to std::unordered_map object.

// The configuration file will take the following format.
/*
; Comment line: Something.ini
[section1]
name1=value1
name2=value2
;;
[section2]
name1=value1
name2=value2
;;
 */

#pragma once

#include <string>
#include <list>
#include <vector>
#include <unordered_map>

#include <iostream>
#include <fstream>
#include <oz++/KeyValueSplitter.h>
#include <oz++/Exception.h>


namespace OZ {

class ConfigParser {
private:
  KeyValueSplitter splitter;

  // The key of the following map will take a string of the format 
  // sectioned_name = section + ":" + name, and a value take a string only.
  std::unordered_map<std::string, std::string> map;

public:
  ConfigParser()
  {
  }

  void parse(const std::string& filename)
  {
    std::ifstream ifs(filename);
    if (!ifs) {
      throw IException("File not found %s", filename.c_str());
    }
    std::string line;

    //Empty section
    std::string section = "";

    while (!ifs.eof()) {
      std::getline(ifs, line);
      std::string trimmed = splitter.trim(line);
      //printf("trimmed %s¥n", trimmed.c_str());
      if (trimmed.empty()) {
        //emtpy line
        continue;
      }
      if (trimmed.find(";") == 0) { 
        //comment
        continue;
      }
      
      std::string new_section= "";
      if (is_section(trimmed, new_section)) {
        section = new_section;
      } 
      
      parse_line(section, trimmed);
    }
  }

  //If a trimmed string is of the format "[Section]", then return true else false.
  bool is_section(const std::string& string, std::string& section)
  { 
    bool found = false;
    const char& head = string.at(0);
    const char& tail = string.at(string.length()-1);
    if (head == '[' && tail== ']') {
      std::string sec = string.substr(1, string.length()-2);
      // Imagine section may have a format [  Foo ], not [Foo].
      section = splitter.trim(sec); 
      //printf("Section %s\n", section.c_str()); 
      found = true;
    }
    return found;
  }

  void parse_line(const std::string section, const std::string& trimmed)
  {
      std::string colon = ":";
      std::string name, value;
      // Split a string of the format name = value to be name and value.
      if (splitter.split(trimmed, name, value)) {
        //printf("splitted '%s'|'%s'\n", name.c_str(), value.c_str());
        std::string env;
        // This is an experimental code to handle the case
        // that a value may have a format something like 
        // dataset = $(HOME)/work/dataset/
        if (splitter.find_env_variable(value.c_str(), env)) {
          //If value contains an env_liken substring something like $(HOME) 
          //printf("env '%s'\n", env.c_str());
          std::string envv = getenv(env.c_str());
          if (!envv.empty()) {
            //printf("env '%s'\n", envv.c_str());
            std::string renv= std::string("$(") + env + std::string(")");
   
            //printf("%s to be replaced to '%s'\n", renv.c_str(), envv.c_str());
            value.find(renv);
            auto pos = value.find(renv); 
            auto len = renv.length();
            if (pos != std::string::npos) {
              value.replace(pos, len, envv);
             
              std::string sectioned_key = section + colon + name;
              /*printf("sectioned_key %s expanded value '%s'\n", 
                      sectioned_key.c_str(),
                  value.c_str());
              */
              map[sectioned_key] = value;
            }
          } else {
            //Not found by getenv() function
            std::string sectioned_key = section + colon + name;
            map[sectioned_key] = value;
          }
        } else {
          //No env_liken something like $(HOME) found.
          std::string sectioned_key = section + colon + name;
          map[sectioned_key] = value;
        }
      }
  }

  ///////////////////////////////////////////////
  //Getters
  const std::string& get(const char* section, const char* name)
  {
    std::string colon=":";
    std::string sectioned_key = std::string(section) + colon
                              + std::string(name);
    return map[sectioned_key];
  }

  const std::string& get(const std::string section, const std::string& name)
  {
    std::string colon=":";
    std::string sectioned_key = section + colon + name;
    return map[sectioned_key];
  }


  const char* getString(const char* section, const char* name)
  {
    const std::string& string = get(section,  name);
    return string.c_str(); 
  }

  int getInt(const char* section, const char* name)
  {
    const std::string& string = get(section,  name);
    const char* cstring= string.c_str(); 
    return atoi(cstring);
  }

  float getFloat(const char* section, const char* name)
  {
    const std::string& string = get(section,  name);
    const char* cstring= string.c_str();
    return atof(cstring);
  }


  bool getBool(const char* section, const char* name)
  {
    bool rc = false;
    const std::string& string = get(section,  name);
    if (string == "true" || string == "True") {
      rc = true;
    }
    return rc;
  }

  ///////////////////////////////////////////////
  //Setters
  void set(const std::string section, const std::string& name,
                 const std::string& value)
  {
    std::string colon=":";
    std::string sectioned_key = section + colon + name;
    map[sectioned_key] = value;
  }

  void setString(const std::string section, const std::string& name,
                 const std::string& value)
  {
    std::string colon=":";
    std::string sectioned_key = section + colon + name;
    map[sectioned_key] = value;
  }

  void setInt(const std::string section, const std::string& name,
                 int value)
  {
    std::string colon=":";
    std::string sectioned_key = section + colon + name;
    char buff[20];
    sprintf(buff,"%d", value);
    map[sectioned_key] = buff;
  }

  void setFloat(const std::string section, const std::string& name,
                 float value) //double value
  {
    std::string colon=":";
    std::string sectioned_key = section + colon + name;
    char buff[100];
    sprintf(buff,"%lf", value); 
    map[sectioned_key] = buff;
  }

  void setBool(const std::string section, const std::string& name,
                 bool value)
  {
    std::string colon=":";
    std::string sectioned_key = section + colon + name;
    char buff[20];
    if (value) {
      sprintf(buff,"%s", "true");
    } else {
      sprintf(buff,"%s", "false");
    }
    map[sectioned_key] = buff;
  }

  void dump()
  {
    for(auto it = map.begin(); it != map.end(); it++) {
      std::cout << "key = " << it->first
                << ", val = " << it->second << "\n";
    }
  }

  
  static bool compareByFirst(std::pair<std::string, std::string>& a, 
                      std::pair<std::string, std::string>& b) 
  {
    if ( a.first.compare(b.first) < 0) {
      return true;
    } else {
       return false;
    }
  }

  //
  void save(std::string& filename)
  {
    std::ofstream ofs(filename);
    if (!ofs) {
      throw IException("File not found %s", filename.c_str());
    }
    // 1. Get sorted slist from the map (std::unorder_map).
    std::list<std::pair<std::string, std::string>> slist;

    sort(slist);

    std::string section = "";
    std::string colon = ":";
    std::string sep   = " = ";
    bool first_section = true;
    //
    for(auto it = slist.begin(); it != slist.end(); it++) {

      std::string sectioned_key = it->first; 

      std::string sec, key; 
      // 2. Split the sectioned_key to sec and key.
      splitter.split(sectioned_key, colon, sec, key);
      if (section != sec) {
        section = sec;
        // If found the first section.
        if (first_section) {
          // Write the following comment line which contains filename
          ofs <<"; " << filename <<std::endl;
          first_section = false;
        }
        // 3. Write the section.
        ofs<< std::endl<< "[" << section << "]" << std::endl;
      } 
      // 4. Write key = value
      ofs<< key << sep << it->second << std::endl;
    }
  }

  //
  void sort(std::list<std::pair<std::string, std::string>>& slist)
  {
    // 1. Create a list of pairs from the map. 
    for(auto it = map.begin(); it != map.end(); it++) {
      std::pair<std::string, std::string> pair(it->first, it->second);
      slist.push_back(pair);
    }
     
    // 2. Sort the list by the first of each pair.
    slist.sort(compareByFirst); 

    
    printf("Sorted list .................\n");
    for(auto it = slist.begin(); it != slist.end(); it++) {
      std::cout << "key = " << it->first
                << ", val = " << it->second << "\n";
    }
  }

};

}