// ------------------------------------------------------------------------------------------------
#include "Library/IO/INI.hpp"

// ------------------------------------------------------------------------------------------------
#include <cerrno>

// ------------------------------------------------------------------------------------------------
namespace SqMod {

// ------------------------------------------------------------------------------------------------
SQMODE_DECL_TYPENAME(ResultTypename, _SC("SqIniResult"))
SQMODE_DECL_TYPENAME(EntriesTypename, _SC("SqIniEntries"))
SQMODE_DECL_TYPENAME(DocumentTypename, _SC("SqIniDocument"))

// ------------------------------------------------------------------------------------------------
void IniResult::Check() const
{
    // Identify the error type
    switch (m_Result)
    {
        case SI_FAIL:
            STHROWF("Unable to %s. Probably invalid", m_Action.c_str());
        break;
        case SI_NOMEM:
            STHROWF("Unable to %s. Ran out of memory", m_Action.c_str());
        break;
        case SI_FILE:
            STHROWF("Unable to %s. %s", strerror(errno));
        break;
        case SI_OK:
        case SI_UPDATED:
        case SI_INSERTED:
            break; /* These are not error messahes. */
        default:
            STHROWF("Unable to %s for some unforeseen reason", m_Action.c_str());
    }
}

// ------------------------------------------------------------------------------------------------
void IniDocumentRef::Validate() const
{
    // Is the document handle valid?
    if (!m_Ptr)
    {
        STHROWF("Invalid INI document reference");
    }
}

// ------------------------------------------------------------------------------------------------
Int32 IniEntries::Cmp(const IniEntries & o) const
{
    if (m_Elem == o.m_Elem)
    {
        return 0;
    }
    else if (m_List.size() > o.m_List.size())
    {
        return 1;
    }
    else
    {
        return -1;
    }
}

// ------------------------------------------------------------------------------------------------
void IniEntries::Next()
{
    // Are there any other elements ahead?
    if (!m_List.empty() && m_Elem != m_List.end())
    {
        ++m_Elem; // Go ahead one element
    }
}

// ------------------------------------------------------------------------------------------------
void IniEntries::Prev()
{
    // Are there any other elements behind?
    if (!m_List.empty() && m_Elem != m_List.begin())
    {
        --m_Elem; // Go back one element
    }
}

// ------------------------------------------------------------------------------------------------
void IniEntries::Advance(Int32 n)
{
    // Are there any other elements ahead?
    if (m_List.empty() || m_Elem == m_List.end())
    {
        return;
    }
    // Jump as many elements as possible within the specified distance
    while ((--n >= 0) && m_Elem != m_List.end())
    {
        ++m_Elem;
    }
}

// ------------------------------------------------------------------------------------------------
void IniEntries::Retreat(Int32 n)
{
    // Are there any other elements behind?
    if (m_List.empty() || m_Elem == m_List.begin())
    {
        return;
    }
    // Jump as many elements as possible within the specified distance
    while ((--n >= 0) && m_Elem != m_List.begin())
    {
        --m_Elem;
    }
}

// ------------------------------------------------------------------------------------------------
CSStr IniEntries::GetItem() const
{
    // is the current element valid?
    if (m_List.empty() || m_Elem == m_List.end())
    {
        STHROWF("Invalid INI entry [item]");
    }
    // Return the requested information
    return m_Elem->pItem;
}

// ------------------------------------------------------------------------------------------------
CSStr IniEntries::GetComment() const
{
    // is the current element valid?
    if (m_List.empty() || m_Elem == m_List.end())
    {
        STHROWF("Invalid INI entry [comment]");
    }
    // Return the requested information
    return m_Elem->pComment;
}

// ------------------------------------------------------------------------------------------------
Int32 IniEntries::GetOrder() const
{
    // is the current element valid?
    if (m_List.empty() || m_Elem == m_List.end())
    {
        STHROWF("Invalid INI entry [order]");
    }
    // Return the requested information
    return m_Elem->nOrder;
}

// ------------------------------------------------------------------------------------------------
Int32 IniDocument::Cmp(const IniDocument & o) const
{
    if (m_Doc == o.m_Doc)
    {
        return 0;
    }
    else if (m_Doc.m_Ptr > o.m_Doc.m_Ptr)
    {
        return 1;
    }
    else
    {
        return -1;
    }
}

// ------------------------------------------------------------------------------------------------
IniResult IniDocument::LoadFile(CSStr filepath)
{
    // Validate the handle
    m_Doc.Validate();
    // Attempt to load the file from disk and return the result
    return IniResult("load INI file",  m_Doc->LoadFile(filepath));
}

// ------------------------------------------------------------------------------------------------
IniResult IniDocument::LoadData(CSStr source, Int32 size)
{
    // Validate the handle
    m_Doc.Validate();
    // Attempt to load the file from memory and return the result
    return IniResult("load INI file", m_Doc->LoadData(source, size < 0 ? strlen(source) : size));
}

// ------------------------------------------------------------------------------------------------
IniResult IniDocument::SaveFile(CSStr filepath, bool signature)
{
    // Validate the handle
    m_Doc.Validate();
    // Attempt to save the file to disk and return the result
    return IniResult("save INI file", m_Doc->SaveFile(filepath, signature));
}

// ------------------------------------------------------------------------------------------------
Object IniDocument::SaveData(bool signature)
{
    // Validate the handle
    m_Doc.Validate();
    // The string where the content will be saved
    String source;
    // Attempt to save the data to string
    if (m_Doc->Save(source, signature) < 0)
    {
        STHROWF("Unable to save INI document");
    }
    // Obtain the initial stack size
    const StackGuard sg(SqVM());
    // Transform it into a script object
    sq_pushstring(SqVM(), source.c_str(), source.size());
    // Get the object from the stack and return it
    return Var< Object >(SqVM(), -1).value;
}

// ------------------------------------------------------------------------------------------------
IniEntries IniDocument::GetAllSections() const
{
    // Validate the handle
    m_Doc.Validate();
    // Prepare a container to receive the entries
    static Container entries;
    // Obtain all sections from the INI document
    m_Doc->GetAllSections(entries);
    // Return the entries and take over content
    return IniEntries(m_Doc, entries);
}

// ------------------------------------------------------------------------------------------------
IniEntries IniDocument::GetAllKeys(CSStr section) const
{
    // Validate the handle
    m_Doc.Validate();
    // Prepare a container to receive the entries
    static Container entries;
    // Obtain all sections from the INI document
    m_Doc->GetAllKeys(section, entries);
    // Return the entries and take over content
    return IniEntries(m_Doc, entries);
}

// ------------------------------------------------------------------------------------------------
IniEntries IniDocument::GetAllValues(CSStr section, CSStr key) const
{
    // Validate the handle
    m_Doc.Validate();
    // Prepare a container to receive the entries
    static Container entries;
    // Obtain all sections from the INI document
    m_Doc->GetAllValues(section, key, entries);
    // Return the entries and take over content
    return IniEntries(m_Doc, entries);
}

// ------------------------------------------------------------------------------------------------
Int32 IniDocument::GetSectionSize(CSStr section) const
{
    // Validate the handle
    m_Doc.Validate();
    // Return the requested information
    return m_Doc->GetSectionSize(section);
}

// ------------------------------------------------------------------------------------------------
bool IniDocument::HasMultipleKeys(CSStr section, CSStr key) const
{
    // Validate the handle
    m_Doc.Validate();
    // Where to retrive whether the key has multiple instances
    bool multiple = false;
    // Attempt to query the information
    if (m_Doc->GetValue(section, key, nullptr, &multiple) == nullptr)
    {
        return true; // Doesn't exist
    }
    // Return the result
    return multiple;
}

// ------------------------------------------------------------------------------------------------
CCStr IniDocument::GetValue(CSStr section, CSStr key, CSStr def) const
{
    // Validate the handle
    m_Doc.Validate();
    // Attempt to query the information and return it
    return m_Doc->GetValue(section, key, def, nullptr);
}

// ------------------------------------------------------------------------------------------------
SQInteger IniDocument::GetInteger(CSStr section, CSStr key, SQInteger def) const
{
    // Validate the handle
    m_Doc.Validate();
    // Attempt to query the information and return it
    return static_cast< SQInteger >(m_Doc->GetLongValue(section, key, def, nullptr));
}

// ------------------------------------------------------------------------------------------------
SQFloat IniDocument::GetFloat(CSStr section, CSStr key, SQFloat def) const
{
    // Validate the handle
    m_Doc.Validate();
    // Attempt to query the information and return it
    return static_cast< SQFloat >(m_Doc->GetDoubleValue(section, key, def, nullptr));
}

// ------------------------------------------------------------------------------------------------
bool IniDocument::GetBoolean(CSStr section, CSStr key, bool def) const
{
    // Validate the handle
    m_Doc.Validate();
    // Attempt to query the information and return it
    return m_Doc->GetBoolValue(section, key, def, nullptr);
}

// ------------------------------------------------------------------------------------------------
IniResult IniDocument::SetValue(CSStr section, CSStr key, CSStr value, bool force, CSStr comment)
{
    // Validate the handle
    m_Doc.Validate();
    // Attempt to apply the specified information and return the result
    return IniResult("set INI value", m_Doc->SetValue(section, key, value, comment, force));
}

// ------------------------------------------------------------------------------------------------
IniResult IniDocument::SetInteger(CSStr section, CSStr key, SQInteger value, bool hex, bool force, CSStr comment)
{
    // Validate the handle
    m_Doc.Validate();
    // Attempt to apply the specified information and return the result
    return IniResult("set INI integer", m_Doc->SetLongValue(section, key, value, comment, hex, force));
}

// ------------------------------------------------------------------------------------------------
IniResult IniDocument::SetFloat(CSStr section, CSStr key, SQFloat value, bool force, CSStr comment)
{
    // Validate the handle
    m_Doc.Validate();
    // Attempt to apply the specified information and return the result
    return IniResult("set INI float", m_Doc->SetDoubleValue(section, key, value, comment, force));
}

// ------------------------------------------------------------------------------------------------
IniResult IniDocument::SetBoolean(CSStr section, CSStr key, bool value, bool force, CSStr comment)
{
    // Validate the handle
    m_Doc.Validate();
    // Attempt to apply the specified information
    return IniResult("set INI boolean", m_Doc->SetBoolValue(section, key, value, comment, force));
}

// ------------------------------------------------------------------------------------------------
bool IniDocument::DeleteValue(CSStr section, CSStr key, CSStr value, bool empty)
{
    // Validate the handle
    m_Doc.Validate();
    // Attempt to remove the specified value and return the result
    return m_Doc->DeleteValue(section, key, value, empty);
}

// ================================================================================================
void Register_INI(HSQUIRRELVM vm)
{
    Table inins(vm);

    inins.Bind(_SC("Result"),
        Class< IniResult >(vm, ResultTypename::Str)
        // Constructors
        .Ctor()
        .Ctor< CSStr, SQInteger >()
        .Ctor< const IniResult & >()
        // Core Meta-methods
        .SquirrelFunc(_SC("_typename"), &ResultTypename::Fn)
        .Func(_SC("_tostring"), &IniResult::ToString)
        .Func(_SC("cmp"), &IniResult::Cmp)
        // Properties
        .Prop(_SC("Valid"), &IniResult::IsValid)
        .Prop(_SC("Action"), &IniResult::GetAction)
        .Prop(_SC("Result"), &IniResult::GetResult)
        // Member Methods
        .Func(_SC("Check"), &IniResult::Check)
    );

    inins.Bind(_SC("IniEntries"),
        Class< IniEntries >(vm, EntriesTypename::Str)
        // Constructors
        .Ctor()
        .Ctor< const IniEntries & >()
        // Core Meta-methods
        .SquirrelFunc(_SC("_typename"), &EntriesTypename::Fn)
        .Func(_SC("_tostring"), &IniEntries::ToString)
        .Func(_SC("cmp"), &IniEntries::Cmp)
        // Properties
        .Prop(_SC("Valid"), &IniEntries::IsValid)
        .Prop(_SC("Empty"), &IniEntries::IsEmpty)
        .Prop(_SC("References"), &IniEntries::GetRefCount)
        .Prop(_SC("Size"), &IniEntries::GetSize)
        .Prop(_SC("Item"), &IniEntries::GetItem)
        .Prop(_SC("Comment"), &IniEntries::GetComment)
        .Prop(_SC("Order"), &IniEntries::GetOrder)
        // Member Methods
        .Func(_SC("Reset"), &IniEntries::Reset)
        .Func(_SC("Next"), &IniEntries::Next)
        .Func(_SC("Prev"), &IniEntries::Prev)
        .Func(_SC("Advance"), &IniEntries::Advance)
        .Func(_SC("Retreat"), &IniEntries::Retreat)
        .Func(_SC("Sort"), &IniEntries::Sort)
        .Func(_SC("SortByKeyOrder"), &IniEntries::SortByKeyOrder)
        .Func(_SC("SortByLoadOrder"), &IniEntries::SortByLoadOrder)
    );

    inins.Bind(_SC("IniDocument"),
        Class< IniDocument, NoCopy< IniDocument > >(vm, DocumentTypename::Str)
        // Constructors
        .Ctor()
        .Ctor< bool >()
        .Ctor< bool, bool >()
        .Ctor< bool, bool, bool >()
        // Core Meta-methods
        .SquirrelFunc(_SC("_typename"), &DocumentTypename::Fn)
        .Func(_SC("_tostring"), &IniDocument::ToString)
        .Func(_SC("cmp"), &IniDocument::Cmp)
        // Properties
        .Prop(_SC("Valid"), &IniDocument::IsValid)
        .Prop(_SC("Empty"), &IniDocument::IsEmpty)
        .Prop(_SC("References"), &IniDocument::GetRefCount)
        .Prop(_SC("Unicode"), &IniDocument::GetUnicode, &IniDocument::SetUnicode)
        .Prop(_SC("MultiKey"), &IniDocument::GetMultiKey, &IniDocument::SetMultiKey)
        .Prop(_SC("MultiLine"), &IniDocument::GetMultiLine, &IniDocument::SetMultiLine)
        .Prop(_SC("Spaces"), &IniDocument::GetSpaces, &IniDocument::SetSpaces)
        // Member Methods
        .Func(_SC("Reset"), &IniDocument::Reset)
        .Func(_SC("LoadFile"), &IniDocument::LoadFile)
        .Overload< IniResult (IniDocument::*)(CSStr) >(_SC("LoadString"), &IniDocument::LoadData)
        .Overload< IniResult (IniDocument::*)(CSStr, Int32) >(_SC("LoadString"), &IniDocument::LoadData)
        .Overload< IniResult (IniDocument::*)(CSStr) >(_SC("SaveFile"), &IniDocument::SaveFile)
        .Overload< IniResult (IniDocument::*)(CSStr, bool) >(_SC("SaveFile"), &IniDocument::SaveFile)
        .Func(_SC("SaveData"), &IniDocument::SaveData)
        .Func(_SC("GetSections"), &IniDocument::GetAllSections)
        .Func(_SC("GetKeys"), &IniDocument::GetAllKeys)
        .Func(_SC("GetValues"), &IniDocument::GetAllValues)
        .Func(_SC("GetSectionSize"), &IniDocument::GetSectionSize)
        .Func(_SC("HasMultipleKeys"), &IniDocument::HasMultipleKeys)
        .Func(_SC("GetValue"), &IniDocument::GetValue)
        .Func(_SC("GetInteger"), &IniDocument::GetInteger)
        .Func(_SC("GetFloat"), &IniDocument::GetFloat)
        .Func(_SC("GetBoolean"), &IniDocument::GetBoolean)
        .Overload< IniResult (IniDocument::*)(CSStr, CSStr, CSStr) >(_SC("SetValue"), &IniDocument::SetValue)
        .Overload< IniResult (IniDocument::*)(CSStr, CSStr, CSStr, bool) >(_SC("SetValue"), &IniDocument::SetValue)
        .Overload< IniResult (IniDocument::*)(CSStr, CSStr, CSStr, bool, CSStr) >(_SC("SetValue"), &IniDocument::SetValue)
        .Overload< IniResult (IniDocument::*)(CSStr, CSStr, SQInteger) >(_SC("SetInteger"), &IniDocument::SetInteger)
        .Overload< IniResult (IniDocument::*)(CSStr, CSStr, SQInteger, bool) >(_SC("SetInteger"), &IniDocument::SetInteger)
        .Overload< IniResult (IniDocument::*)(CSStr, CSStr, SQInteger, bool, bool) >(_SC("SetInteger"), &IniDocument::SetInteger)
        .Overload< IniResult (IniDocument::*)(CSStr, CSStr, SQInteger, bool, bool, CSStr) >(_SC("SetInteger"), &IniDocument::SetInteger)
        .Overload< IniResult (IniDocument::*)(CSStr, CSStr, SQFloat) >(_SC("SetFloat"), &IniDocument::SetFloat)
        .Overload< IniResult (IniDocument::*)(CSStr, CSStr, SQFloat, bool) >(_SC("SetFloat"), &IniDocument::SetFloat)
        .Overload< IniResult (IniDocument::*)(CSStr, CSStr, SQFloat, bool, CSStr) >(_SC("SetFloat"), &IniDocument::SetFloat)
        .Overload< IniResult (IniDocument::*)(CSStr, CSStr, bool) >(_SC("SetBoolean"), &IniDocument::SetBoolean)
        .Overload< IniResult (IniDocument::*)(CSStr, CSStr, bool, bool) >(_SC("SetBoolean"), &IniDocument::SetBoolean)
        .Overload< IniResult (IniDocument::*)(CSStr, CSStr, bool, bool, CSStr) >(_SC("SetBoolean"), &IniDocument::SetBoolean)
        .Overload< bool (IniDocument::*)(CSStr) >(_SC("DeleteValue"), &IniDocument::DeleteValue)
        .Overload< bool (IniDocument::*)(CSStr, CSStr) >(_SC("DeleteValue"), &IniDocument::DeleteValue)
        .Overload< bool (IniDocument::*)(CSStr, CSStr, CSStr) >(_SC("DeleteValue"), &IniDocument::DeleteValue)
        .Overload< bool (IniDocument::*)(CSStr, CSStr, CSStr, bool) >(_SC("DeleteValue"), &IniDocument::DeleteValue)
    );

    RootTable(vm).Bind(_SC("SqIni"), inins);

    ConstTable(vm).Enum(_SC("SqIniError"), Enumeration(vm)
        .Const(_SC("Ok"),                       Int32(SI_OK))
        .Const(_SC("Updated"),                  Int32(SI_UPDATED))
        .Const(_SC("Inserted"),                 Int32(SI_INSERTED))
        .Const(_SC("Fail"),                     Int32(SI_FAIL))
        .Const(_SC("NoMem"),                    Int32(SI_NOMEM))
        .Const(_SC("File"),                     Int32(SI_FILE))
    );
}

} // Namespace:: SqMod