/*
 * Copyright (c) 2014, Chris Johns <chrisj@rtems.org>
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */
/**
 * @file
 *
 * @ingroup rtems_rld
 *
 * @brief INI Configuration reader.
 *
 */

#include <errno.h>

#include <rld-config.h>

#include <SimpleIni.h>

namespace rld
{
  namespace config
  {
    item::item (const std::string& text)
      : text (text)
    {
    }

    item::item (const char* text)
      : text (text)
    {
    }

    bool
    section::has_record (const std::string& name) const
    {
      for (records::const_iterator ri = recs.begin ();
           ri != recs.end ();
           ++ri)
      {
        if ((*ri).name == name)
          return true;
      }
      return false;
    }

    const record&
    section::get_record (const std::string& name) const
    {
      for (records::const_iterator ri = recs.begin ();
           ri != recs.end ();
           ++ri)
      {
        if ((*ri).name == name)
          return *ri;
      }

      throw rld::error ("not found", "config record: " + this->name + '/' + name);
    }

    const std::string
    section::get_record_item (const std::string& rec_name) const
    {
      const record& rec = get_record (rec_name);
      if (rec.items_.size () != 1)
        throw rld::error ("duplicate", "record item: " + name + '/' + rec_name);
      return rec.items_[0].text;
    }

    void
    section::get_record_items (const std::string& rec_name, rld::strings& items) const
    {
      const record& rec = get_record (rec_name);
      items.clear ();
      for (rld::config::items::const_iterator ii = rec.items_.begin ();
           ii != rec.items_.end ();
           ++ii)
      {
        items.push_back ((*ii).text);
      }
    }

    config::config(const std::string& search_path)
    {
      set_search_path (search_path);
    }

    config::~config()
    {
    }

    void
    config::set_search_path (const std::string& search_path)
    {
      if (!search_path.empty ())
        rld::path::path_split (search_path, search);
    }

    void
    config::clear ()
    {
      secs.clear ();
    }

    void
    config::load (const std::string& path)
    {
      CSimpleIniCaseA ini (false, true, true);

      std::string checked_path;

      if (rld::path::check_file (path))
      {
        checked_path = path;
      }
      else
      {
        bool found = false;
        for (rld::path::paths::const_iterator spi = search.begin ();
             spi != search.end ();
             ++spi)
        {
          rld::path::path_join (*spi, path, checked_path);
          if (rld::path::check_file (checked_path))
          {
            found = true;
            break;
          }
        }
        if (!found)
          throw rld::error ("Not found.", "load config: " + path);
      }

      if (ini.LoadFile (checked_path.c_str ()) != SI_OK)
        throw rld::error (::strerror (errno), "load config: " + path);

      paths_.push_back (checked_path);

      /*
       * Merge the loaded configuration into our configuration.
       */

      CSimpleIniCaseA::TNamesDepend skeys;

      ini.GetAllSections(skeys);

      for (CSimpleIniCaseA::TNamesDepend::const_iterator si = skeys.begin ();
           si != skeys.end ();
           ++si)
      {
        section sec;

        sec.name = (*si).pItem;

        CSimpleIniCaseA::TNamesDepend rkeys;

        ini.GetAllKeys((*si).pItem, rkeys);

        for (CSimpleIniCaseA::TNamesDepend::const_iterator ri = rkeys.begin ();
             ri != rkeys.end ();
             ++ri)
        {
          record rec;

          rec.name = (*ri).pItem;

          CSimpleIniCaseA::TNamesDepend vals;

          ini.GetAllValues((*si).pItem, (*ri).pItem, vals);

          for (CSimpleIniCaseA::TNamesDepend::const_iterator vi = vals.begin ();
               vi != vals.end ();
               ++vi)
          {
            rec.items_.push_back (item ((*vi).pItem));
          }

          sec.recs.push_back (rec);
        }

        secs.push_back (sec);

        if (sec.name == "includes")
          includes(sec);
      }
    }


    void
    config::includes (const section& sec, bool must_exist)
    {
      bool have_includes = false;

      try
      {
        rld::strings is;
        parse_items (sec, "include", is);

        have_includes = true;

        /*
         * Include records are a paths which we can load.
         */

        for (rld::strings::const_iterator isi = is.begin ();
             isi != is.end ();
             ++isi)
        {
          load (*isi);
        }
      }
      catch (rld::error re)
      {
        /*
         * No include records, must be all inlined. If we have includes it must
         * be another error so throw it.
         */
        if (have_includes || (!have_includes && must_exist))
          throw;
      }
    }

    const section&
    config::get_section (const std::string& name) const
    {
      for (sections::const_iterator si = secs.begin ();
           si != secs.end ();
           ++si)
      {
        if ((*si).name == name)
          return *si;
      }

      throw error ("not found", "config section: " + name);
    }

    const paths&
    config::get_paths () const
    {
      return paths_;
    }
  }
}
