/////////////////////////////////////////////////////////////////////////////
// Name:        packageopt.cpp
// Purpose:     wxPackageCommandOption, wxPackageCommandOptionArray
// Author:      Julian Smart, Francesco Montorsi, Daniel J. Lauk
// Modified by:
// Created:     2004-11-07
// RCS-ID:      $Id: packageopt.cpp,v 1.4 2007/01/28 14:35:29 frm Exp $
// Copyright:   (c) Julian Smart
// Licence:     wxWidgets licence
/////////////////////////////////////////////////////////////////////////////


// For compilers that support precompilation, includes "wx/wx.h".
#include "wx/wxprec.h"

#ifdef __BORLANDC__
#pragma hdrstop
#endif

#ifndef WX_PRECOMP
    #include "wx/wx.h"
#endif

// includes
#include <wx/fileconf.h>
#include <wx/filename.h>

#include "wxp/packagecmd.h"
#include "wxp/packageinfo.h"


// definitions

wxString wxPackageBuildSystemStageName[] =
{
    _T("Build"),
    _T("Install"),
    _T("Uninstall"),
    _T("Clean")
};

wxString wxPackageCommandOptionCategoryName[] =
{
    _T("listed-values"),
    _T("directory"),
    _T("file"),
    _T("string")
};


// RTTI
IMPLEMENT_DYNAMIC_CLASS(wxPackageCondition, wxObject)
IMPLEMENT_DYNAMIC_CLASS(wxPackageCommandOption, wxObject)


// wx array implementations
#include <wx/arrimpl.cpp> // this is a magic incantation which must be done!
WX_DEFINE_OBJARRAY(wxPackageCommandOptionArrayHelper);


// global objects
wxPackageCommandOption wxEmptyPackageCommandOption;
wxPackageCommandOptionArray wxEmptyPackageCommandOptionArray;



// ----------------------------------------------------------------------------
// string2enum helpers
// ----------------------------------------------------------------------------

wxIMPLEMENT_STRING2COMBINEABLE_ENUM(PackageBuildSystemStage, wxPBSS_MAX)
wxIMPLEMENT_STRING2ENUM(PackageCommandOptionCategory, wxPCOC_MAX)


// ----------------------------------------------------------------------------
// wxPackageCommandOption
// ----------------------------------------------------------------------------

bool wxPackageCommandOption::operator==(const wxPackageCommandOption &opt) const
{
    // check string data
    if (m_strName != opt.m_strName ||
        m_strAllowedValues != opt.m_strAllowedValues ||
        m_strDefValue != opt.m_strDefValue ||
        m_strValue != opt.m_strValue ||
        m_strDesc != opt.m_strDesc ||
        m_strGroup != opt.m_strGroup ||
        m_strValuesDesc != opt.m_strValuesDesc)
        return false;

    if (m_nCat != opt.m_nCat ||
        m_cond != opt.m_cond)
        return false;

    // everything is identic
    return true;
}

void wxPackageCommandOption::AutodetectCategory()
{
    if (!m_strAllowedValues.IsEmpty())
    {
        m_nCat = wxPCOC_LISTED_VALUES;
        return;
    }

    // FIXME: how to autodetect wxPCOC_DIRECTORY and wxPCOC_FILE ?
    //if (m_strName.EndsWith(wxT("DIR"))) m_nCat = wxPCOC_DIRECTORY;

    m_nCat = wxPCOC_STRING;
}

bool wxPackageCommandOption::IsOk() const
{
    if (m_strName.IsEmpty() || m_nCat == wxPCOC_INVALID || m_strGroup.IsEmpty())
        return false;
    if (!GetCondition().IsOk())
        return false;

    switch (m_nCat)
    {
    case wxPCOC_LISTED_VALUES:
        if (m_strAllowedValues.IsEmpty())
            return false;
        if (!m_strValuesDesc.IsEmpty() &&
            GetAllowedValuesDescArray().GetCount() != GetAllowedValuesArray().GetCount())
            return false;
        if (!m_strValue.IsEmpty() &&
            GetAllowedValuesArray().Index(m_strValue) == wxNOT_FOUND)
            return false;
        if (!m_strDefValue.IsEmpty() &&
            GetAllowedValuesArray().Index(m_strDefValue) == wxNOT_FOUND)
            return false;
        break;

    default:
        // cannot do any other check for other categories
        // (we cannot do existence checks here for wxPCOC_FILE and wxPCOC_DIRECTORY!)
        break;
    }

    return true;
}



// ----------------------------------------------------------------------------
// wxPackageCommandOptionArray
// ----------------------------------------------------------------------------

wxString wxJoinOptionWithValue(const wxString &optname, const wxString &value)
{
#if 0
    // use double quotes around the argument only if strictly necessary
    if (value.Contains(wxT(" ")))
        return wxString::Format(wxT("%s=\"%s\""), optname.c_str(), value.c_str());
    return wxString::Format(wxT("%s=%s"), optname.c_str(), value.c_str());
#else
    // always use double quotes aroung the argument:
    // if the argument is a $(key) it may later be substituted with an empty
    // value and it's not nice to see lots of OPT_NAME= strings around
    return wxString::Format(wxT("%s=\"%s\""), optname.c_str(), value.c_str());
#endif
}

wxString wxPackageCommandOptionArray::GetAsList() const
{
    wxString ret;

    // micro optimization
    ret.Alloc(GetCount()*15);

    for (size_t j=0; j<GetCount(); j++)
        ret += wxJoinOptionWithValue(Item(j).GetName(),
                                     Item(j).GetValue()) + wxT(" ");

    // remove any eventually present space from the end
    ret.Trim();
    return ret;
}

wxStringHashMap wxPackageCommandOptionArray::GetSubstitutionOptionsHashMap(
                            long flags, const wxPackageCondition &cond) const
{
    wxStringHashMap ret;

    wxMergeHashMap(ret, GetGroupNamesHashMap(flags, cond));
    wxMergeHashMap(ret, GetTypeNamesHashMap(flags, cond));

    return ret;
}

wxStringHashMap wxPackageCommandOptionArray::GetGroupNamesHashMap(
                            long flags, const wxPackageCondition &cond) const
{
    wxStringHashMap ret;

    wxArrayString groups(GetAllOptionGroups());
    for (size_t i=0; i < groups.GetCount(); i++)
    {
        // add to 'value' only the options which belong to the i-th group
        wxString value;
        for (size_t j=0; j<GetCount(); j++)
        {
            if (Item(j).GetGroup() != groups[i])
                continue;

            if ((flags & wxPKGCMD_ONLY_CHANGED) != 0 &&
                !Item(j).IsChanged())
                continue;

            if ((flags & wxPKGCMD_ONLY_MATCHING_CONDITION) != 0 &&
                !Item(j).GetCondition().Matches(cond))
                continue;

            // even if all previous tests passed, don't substitute options
            // with empty values as they would not be correctly interpreted
            // by make processes!
            if (Item(j).GetValue().IsEmpty())
                continue;       // skip it!

            value += wxJoinOptionWithValue(Item(j).GetName(),
                                           Item(j).GetValue()) + wxT(" ");
        }

        // remove any eventually present space from the end
        ret[groups[i].Lower() + wxT("options")] = value.Trim();
    }

    return ret;
}

wxStringHashMap wxPackageCommandOptionArray::GetTypeNamesHashMap(
                            long flags, const wxPackageCondition &cond) const
{
    wxStringHashMap ret;

    wxArrayString types(3, wxPackageBuildSystemStageName);
    for (size_t i=0; i < types.GetCount(); i++)
    {
        // add to 'value' only the options which belong to the i-th type
        wxString value;
        for (size_t j=0; j<GetCount(); j++)
        {
            if (!Item(j).GetCondition().IsValidForStage(wxIdx2PackageBuildSystemStage(i)))
                continue;

            if ((flags & wxPKGCMD_ONLY_CHANGED) != 0 &&
                !Item(j).IsChanged())
                continue;

            if ((flags & wxPKGCMD_ONLY_MATCHING_CONDITION) != 0 &&
                !Item(j).GetCondition().Matches(cond))
                continue;

            // even if all previous tests passed, don't substitute options
            // with empty values as they would not be correctly interpreted
            // by make processes!
            if (Item(j).GetValue().IsEmpty())
                continue;       // skip it!

            value += wxJoinOptionWithValue(Item(j).GetName(),
                                           Item(j).GetValue()) + wxT(" ");
        }


        // remove any eventually present space from the end
        ret[types[i].Lower() + wxT("options")] = value.Trim();
    }

    return ret;
}

wxArrayString wxPackageCommandOptionArray::GetOptionGroups(long stages) const
{
    wxArrayString ret;
    for (size_t i=0; i < GetCount(); i++)
        if (Item(i).GetCondition().IsValidForStages(stages))
            if (ret.Index(Item(i).GetGroup()) == wxNOT_FOUND)
                ret.Add(Item(i).GetGroup());
    return ret;
}

wxArrayString wxPackageCommandOptionArray::GetAllOptionGroups() const
{
    wxArrayString ret;
    for (size_t i=0; i < GetCount(); i++)
        if (ret.Index(Item(i).GetGroup()) == wxNOT_FOUND)
            ret.Add(Item(i).GetGroup());
    return ret;
}

wxPackageCommandOptionArray wxPackageCommandOptionArray::GetChangedOptions() const
{
    wxPackageCommandOptionArray ret;
    for (size_t i=0; i < GetCount(); i++)
        if (Item(i).IsChanged())
            ret.Add(Item(i));
    return ret;
}

bool wxPackageCommandOptionArray::UpdateOption(const wxPackageCommandOption &opt)
{
    int n = IndexByName(opt.GetName());
    if (n == wxNOT_FOUND)
        return false;

    //ItemById(pkg) = pkg;
    RemoveAt(n);
    Insert(opt, n);
    return true;
}

bool wxPackageCommandOptionArray::Contains(const wxPackageCommandOptionArray &arr) const
{
    size_t i, j;
    for (i=0; i<arr.GetCount(); i++)
    {
        for (j=0; j<GetCount(); j++)
            if (Item(j) == arr[i])
                break;

        // if this package does not have the i-th option of the option preset
        // it's not using that preset!
        if (j == GetCount())
            return false;
    }

    return true;
}

bool wxPackageCommandOptionArray::Remove(const wxPackageCommandOptionArray &arr)
{
    bool allfound = true;
    size_t i, j;

    for (i=0; i<arr.GetCount(); i++)
    {
        for (j=0; j<GetCount(); j++)
            if (Item(j) == arr[i])
                break;

        if (j == GetCount())
            allfound = false;
        else
        {
            RemoveAt(j);
            j--;
        }
    }

    return allfound;
}

wxPackageCondition wxPackageCommandOptionArray::GetCommonCondition() const
{
    wxPackageCondition ret;
    ret.SetAlwaysTrue();
    for (size_t i=0; i<GetCount(); i++)
        ret.IntersecateWith(Item(i).GetCondition());

    return ret;
}

