/***************************************************************************
                          SIMPLY POWERFUL TOOLKIT (SPTK)
                          CRegistry.cpp  -  description
                             -------------------
    begin                : Tue Mar 26 2002
    copyright            : (C) 2000-2005 by Alexey S.Parshin
    email                : alexeyp@gmail.com
 ***************************************************************************/

/***************************************************************************
   This library is free software; you can redistribute it and/or modify it 
   under the terms of the GNU Library General Public License as published by
   the Free Software Foundation; either version 2 of the License, or (at 
   your option) any later version.

   This library 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 Library
   General Public License for more details.

   You should have received a copy of the GNU Library General Public License
   along with this library; if not, write to the Free Software Foundation, 
   Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.

   Please report all bugs and problems to "alexeyp@gmail.com"
 ***************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>

#include <sptk3/CRegistry.h>
#include <sptk3/CException.h>
#include <sptk3/xml/CXml.h>
#include <sptk3/string_ext.h>

using namespace std;

#ifndef _WIN32
# include <unistd.h>
#else

# include <windows.h>
# include <io.h>
# include <direct.h>

# define S_ISLNK(m)      (false)
# define S_ISEXEC(m)     (((m) & _S_IEXEC) == _S_IEXEC)
#if !defined(__GNUC__) && !defined(__BORLANDC__)
# define S_ISREG(m)      (((m) & _S_IFREG) == _S_IFREG)
# define S_ISDIR(m)      (((m) & _S_IFDIR) == _S_IFDIR)
# define S_ISBLK(m)      (((m) & _S_IFBLK) == _S_IFBLK)
#endif /* __GNUC__ */

#endif

const string CRegistryItemAttributes::emptyAttribute;

//---------------------------------------------------------------------------
string& CRegistryItemAttributes::operator [] (const char *name) {
   iterator itor = find(name);
   if (itor == end()) {
      map<istring,string>::insert(value_type(name,""));
      itor = find(name);
   }
   return itor->second;
}

const std::string& CRegistryItemAttributes::operator [] (const char *name) const {
   const_iterator itor = find(name);
   if (itor == end())
      return emptyAttribute;
   return itor->second;
}
//---------------------------------------------------------------------------
CRegistryItem::CRegistryItem(const string buffer) {
   if (!buffer.length()) return;
   char *p = strchr((char *)buffer.c_str(),'=');
   if (p) {
      *p = 0;
      p++;
      (*this)["value"] = p;
   }
   m_name = trim(buffer.c_str());
}

CRegistryItem::CRegistryItem(const CRegistryItem& item) { 
   m_name = item.m_name; 
   const_iterator itor = item.begin();
   for (; itor != item.end(); itor++)
      (*this)[itor->first.c_str()] = itor->second;
}

CRegistryItem& CRegistryItem::operator = (unsigned v) {
   char buffer[64];
   sprintf(buffer,"%i",v);
   (*this)["value"] = buffer;
   return *this;
}

CRegistryItem& CRegistryItem::operator = (int v) {
   char buffer[64];
   sprintf(buffer,"%i",v);
   (*this)["value"] = buffer;
   return *this;
}

CRegistryItem& CRegistryItem::operator = (double v) {
   char buffer[64];
   sprintf(buffer,"%f",v);
   (*this)["value"] = buffer;
   return *this;
}

//----------------------------------------------------------------------------
void CRegistryTopic::addItem(string buffer) {
   CRegistryItem *item = new CRegistryItem(buffer);
   iterator itor = find(item->name());
   if (itor != end()) { 
      delete itor->second;
      itor->second = item;
   } else 
      (*this)[item->name()] = item;
}

void CRegistryTopic::clear() {
   for (iterator itor = begin(); itor != end(); itor++)
      delete itor->second;
   CRegistryItems::clear();
}
// ----------------------------------------------------------------------------
string CRegistry::homeDirectory() {
#ifndef _WIN32
   string homeDir = trim(getenv("HOME"));
   if (homeDir.empty()) homeDir = ".";
   homeDir += "/";
#else
   char *hdrive = getenv("HOMEDRIVE");
   char *hdir   = getenv("HOMEPATH");
   if (!hdir && !hdrive) 
      return getenv("WINDIR") + string("\\");

   string homeDrive;
   string homeDir;
   if (hdrive)
      homeDrive = hdrive;
   if (hdir)
      homeDir = hdir;
   if (homeDir == "\\")
      homeDir = homeDrive + "\\";
   else homeDir = homeDrive + "\\" + homeDir;
#endif
   return homeDir;
}

CRegistry::CRegistry(string fileName,string programGroupName,CRegistryMode mode)
: m_fileName(fileName) {
   m_xmlMode = true;
   if (m_fileName.length()) {
      string directory;
      if (mode == USER_REGISTRY)
         directory = homeDirectory();
      if (programGroupName != "") {
         while (programGroupName[0] == '.') programGroupName = programGroupName.substr(1);
         programGroupName = replaceAll(programGroupName,"\\","_");
         programGroupName = replaceAll(programGroupName,"/","_");
      }
      if (programGroupName != "") 
         directory += "." + programGroupName + "/";
      m_fileName = directory + m_fileName;
   }
   m_fileName = replaceAll(m_fileName,"\\","/");
   topic("Default");
}

CRegistry::~CRegistry() {
   clear();   
}

void CRegistry::prepareDirectory() {
   struct stat st;
   unsigned long pos = m_fileName.rfind("/");
   if (pos == string::npos) return;
   string directory = m_fileName.substr(0,pos);
   if (stat(directory.c_str(),&st) == 0) {
      if (!S_ISDIR(st.st_mode))
         throw CException("Can't create directory '"+directory+"'");
   } else {
#ifdef _WIN32
      mkdir(directory.c_str());
#else
      mkdir(directory.c_str(),0770);
#endif
   }
}

void fromXML(CXmlNode *n,CRegistry& registry) {
   if(!n) return;
   if (n->isCDataSection() || n->isComment()) return;

   if (n->type() == CXmlNode::DOM_ELEMENT) {
      string type = lowerCase(n->name());
      string name = n->getAttribute("name");
      if (type == "topic") {
         registry.topic(name);
      } else {
         CRegistryItem& item = registry[name];
         const CXmlAttributes& attributes = n->attributes();
         CXmlAttributes::const_iterator itor = attributes.begin();
         for (; itor != attributes.end(); itor++) {
            item[itor->first.c_str()] = itor->second;
         }
            //string value = n->getAttribute("value");
 		 //string dataType = n->getAttribute("type");
       //registry[name] = value;
      }
   }

   unsigned cnt = n->children();
   for (unsigned a=0; a < cnt; a++) {
      CXmlNode *node = n->child(a);
      fromXML(node,registry);
   }
}

void CRegistry::load(const CStrings& inputData) {
   clear();
   if (inputData[0].find("<!DOCTYPE ") == string::npos) {
      // Reading the old style INI configuration
      m_xmlMode = false;
      for (unsigned row = 0; row < inputData.size(); row++) {
         char *b = (char *)inputData[row].c_str();
         int i = strlen(b) - 1;
         while (i >= 0 && (unsigned char)b[i] < 33) i--;
         b[i+1] = 0;

         if (i <= 0) continue;

         if (b[0] == '[' && b[i] == ']') {
            b[i] = 0;
            topic(b+1);
            continue;
         }

        //fflush(stdout);
         m_currentTopic->addItem((char *)b);
      }
   } else {
      // Reading the new style XML configuration
      m_xmlMode = true;
      CBuffer buffer;

      buffer = inputData.asString("\n");

      CXmlDoc *doc = new CXmlDoc;
      doc->load(buffer);
      fromXML(doc,*this);
   }
}

void CRegistry::load() {
   CStrings inputData;
   inputData.loadFromFile(m_fileName);
   load(inputData);
}

void CRegistry::save(CStrings& outputData) {
   prepareDirectory();
   outputData.clear();
   if (m_xmlMode) {
      CBuffer buffer;
      CXmlDoc doc("Configuration");

      CRegistryTopics::iterator itor = m_topics.begin();
      for (; itor != m_topics.end(); itor++) {
         CRegistryTopic *topic = itor->second;
         if (!topic->size()) continue;
         CXmlNode *section = new CXmlElement(doc,"topic");
         section->setAttribute("name",topic->name());
         CRegistryTopic::iterator itor = topic->begin();
         for (; itor != topic->end(); itor++) {
            CXmlNode *key = new CXmlElement(*section,"key");
            CRegistryItem *item = itor->second;
            key->setAttribute("name",item->name());
            CRegistryItem::const_iterator attributes = item->begin();
            unsigned valueCount = 0;
            for (; attributes != item->end(); attributes++) {
               const string& attributeName = attributes->first;
               const char *  attributeValue = attributes->second.c_str();
               if (attributeValue && attributeValue[0]) {
                  key->setAttribute(attributeName.c_str(),attributeValue);
                  valueCount++;
               }
            }
            if (!valueCount)
               section->remove(key);
         }
      }

      doc.save(buffer);
      outputData.fromString(buffer.data(),"\n");
   } else {
      CRegistryTopics::iterator itor = m_topics.begin();
      for (; itor != m_topics.end(); itor++) {
         CRegistryTopic *topic = itor->second;
         if (!topic->size()) continue;
         string topicName = "[" + topic->name() + "]";
         outputData.push_back(topicName.c_str());
         CRegistryTopic::iterator itor = topic->begin();
         for (; itor != topic->end(); itor++) {
            CRegistryItem *item = itor->second;
            std::string itemData = item->name() + "=" + item->value();
            outputData.push_back(itemData.c_str());
         }
      }
   }
}

void CRegistry::save() {
   CStrings outputData;
   save(outputData);
   outputData.saveToFile(m_fileName);
}

void CRegistry::topic(string atopic) {
   if (m_topic != atopic) {
      m_topic = atopic;

      CRegistryTopics::iterator itor = m_topics.find(atopic);
      if (itor != m_topics.end()) {
         m_currentTopic = itor->second;
         return;
      }

      m_currentTopic = new CRegistryTopic(m_topic);
      m_topics[m_currentTopic->name()] = m_currentTopic;
   }
}

const CRegistryItem& CRegistry::operator[] (const char *name) const {
   CRegistryItems::const_iterator itor = m_currentTopic->find(name);
   if (itor == m_currentTopic->end()) return m_emptyItem;
   return *itor->second;
}

CRegistryItem& CRegistry::operator[] (const char *name) {
   if (!m_currentTopic) topic("Default");
   CRegistryItems::iterator itor = m_currentTopic->find(name);
   if (itor == m_currentTopic->end()) {
      CRegistryItem *item = new CRegistryItem(name);
      (*m_currentTopic)[name] = item;
      return *item;
   }
   return *itor->second;
}

void CRegistry::clear() {
   m_topic = "";
   m_currentTopic = 0L;
   CRegistryTopics::iterator itor = m_topics.begin();
   for (; itor != m_topics.end(); itor++) 
      delete itor->second;
   m_topics.clear();
}   
