/* vim: set sw=4 sts=4 et foldmethod=syntax : */

/*
 * Copyright (c) 2008, 2009, 2010, 2011 Ciaran McCreesh
 *
 * This file is part of the Paludis package manager. Paludis is free software;
 * you can redistribute it and/or modify it under the terms of the GNU General
 * Public License version 2, as published by the Free Software Foundation.
 *
 * Paludis 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 General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
 * Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include <paludis/elike_choices.hh>
#include <paludis/environment.hh>
#include <paludis/permitted_choice_value_parameter_values.hh>
#include <paludis/util/tribool.hh>
#include <paludis/util/stringify.hh>
#include <paludis/util/destringify.hh>
#include <paludis/util/log.hh>
#include <paludis/util/singleton-impl.hh>
#include <paludis/util/enum_iterator.hh>
#include <paludis/util/map.hh>
#include <set>
#include <limits>
#include <istream>
#include <ostream>

using namespace paludis;

#include <paludis/elike_choices-se.cc>

namespace
{
    struct CommonValues :
        Singleton<CommonValues>
    {
        const std::shared_ptr<const PermittedChoiceValueParameterIntegerValue> permitted_jobs_values;

        const std::shared_ptr<Map<std::string, std::string> > permitted_symbols;
        const std::shared_ptr<const PermittedChoiceValueParameterEnumValue> permitted_symbols_values;

        const std::shared_ptr<Map<std::string, std::string> > permitted_work;
        const std::shared_ptr<const PermittedChoiceValueParameterEnumValue> permitted_work_values;

        CommonValues() :
            permitted_jobs_values(std::make_shared<PermittedChoiceValueParameterIntegerValue>(1, std::numeric_limits<int>::max())),
            permitted_symbols(std::make_shared<Map<std::string, std::string> >()),
            permitted_symbols_values(std::make_shared<PermittedChoiceValueParameterEnumValue>(permitted_symbols)),
            permitted_work(std::make_shared<Map<std::string, std::string> >()),
            permitted_work_values(std::make_shared<PermittedChoiceValueParameterEnumValue>(permitted_work))
        {
            for (EnumIterator<ELikeSymbolsChoiceValueParameter> e, e_end(last_escvp) ;
                    e != e_end ; ++e)
            {
                switch (*e)
                {
                    case escvp_split:
                        permitted_symbols->insert("split", "Split debug symbols");
                        continue;
                    case escvp_preserve:
                        permitted_symbols->insert("preserve", "Preserve debug symbols");
                        continue;
                    case escvp_strip:
                        permitted_symbols->insert("strip", "Strip debug symbols");
                        continue;
                    case escvp_compress:
                        permitted_symbols->insert("compress", "Split and compress debug symbols");
                        continue;
                    case last_escvp:
                        break;
                }

                throw InternalError(PALUDIS_HERE, "Unhandled ELikeSymbolsChoiceValueParameter");
            }

            for (EnumIterator<ELikeWorkChoiceValueParameter> e, e_end(last_ewcvp) ;
                    e != e_end ; ++e)
            {
                switch (*e)
                {
                    case ewcvp_tidyup:
                        permitted_work->insert("tidyup", "Tidy up work directory after a successful build");
                        continue;
                    case ewcvp_preserve:
                        permitted_work->insert("preserve", "Preserve the working directory");
                        continue;
                    case ewcvp_remove:
                        permitted_work->insert("remove", "Always remove the working directory");
                        continue;
                    case ewcvp_leave:
                        permitted_work->insert("leave", "Do not remove, but allow destructive merges");
                        continue;
                    case last_ewcvp:
                        break;
                }

                throw InternalError(PALUDIS_HERE, "Unhandled ELikeWorkChoiceValueParameter");
            }
        }
    };
}

const UnprefixedChoiceName
ELikeOptionalTestsChoiceValue::canonical_unprefixed_name()
{
    return UnprefixedChoiceName("optional_tests");
}

const ChoiceNameWithPrefix
ELikeOptionalTestsChoiceValue::canonical_name_with_prefix()
{
    return ChoiceNameWithPrefix(stringify(canonical_build_options_prefix()) + ":" + stringify(canonical_unprefixed_name()));
}

ELikeOptionalTestsChoiceValue::ELikeOptionalTestsChoiceValue(const std::shared_ptr<const PackageID> & id,
        const Environment * const env, const std::shared_ptr<const Choice> & choice,
        const bool l) :
    _enabled((! l) && (env->want_choice_enabled(id, choice, canonical_unprefixed_name()).is_true())),
    _mask(l)
{
}

const UnprefixedChoiceName
ELikeOptionalTestsChoiceValue::unprefixed_name() const
{
    return canonical_unprefixed_name();
}

const ChoiceNameWithPrefix
ELikeOptionalTestsChoiceValue::name_with_prefix() const
{
    return canonical_name_with_prefix();
}

bool
ELikeOptionalTestsChoiceValue::enabled() const
{
    return _enabled;
}

bool
ELikeOptionalTestsChoiceValue::enabled_by_default() const
{
    return false;
}

bool
ELikeOptionalTestsChoiceValue::locked() const
{
    return _mask;
}

const std::string
ELikeOptionalTestsChoiceValue::description() const
{
    return "Run tests considered by the package to be optional";
}

ChoiceOrigin
ELikeOptionalTestsChoiceValue::origin() const
{
    return co_special;
}

const std::string
ELikeOptionalTestsChoiceValue::parameter() const
{
    return "";
}

const std::shared_ptr<const PermittedChoiceValueParameterValues>
ELikeOptionalTestsChoiceValue::permitted_parameter_values() const
{
    return nullptr;
}

bool
ELikeOptionalTestsChoiceValue::presumed() const
{
    return false;
}

const UnprefixedChoiceName
ELikeRecommendedTestsChoiceValue::canonical_unprefixed_name()
{
    return UnprefixedChoiceName("recommended_tests");
}

const ChoiceNameWithPrefix
ELikeRecommendedTestsChoiceValue::canonical_name_with_prefix()
{
    return ChoiceNameWithPrefix(stringify(canonical_build_options_prefix()) + ":" + stringify(canonical_unprefixed_name()));
}

ELikeRecommendedTestsChoiceValue::ELikeRecommendedTestsChoiceValue(const std::shared_ptr<const PackageID> & id,
        const Environment * const env, const std::shared_ptr<const Choice> & choice,
        const bool l) :
    _enabled((! l) && (! env->want_choice_enabled(id, choice, canonical_unprefixed_name()).is_false())),
    _mask(l)
{
}

const UnprefixedChoiceName
ELikeRecommendedTestsChoiceValue::unprefixed_name() const
{
    return canonical_unprefixed_name();
}

const ChoiceNameWithPrefix
ELikeRecommendedTestsChoiceValue::name_with_prefix() const
{
    return canonical_name_with_prefix();
}

bool
ELikeRecommendedTestsChoiceValue::enabled() const
{
    return _enabled;
}

bool
ELikeRecommendedTestsChoiceValue::enabled_by_default() const
{
    return true;
}

bool
ELikeRecommendedTestsChoiceValue::locked() const
{
    return _mask;
}

const std::string
ELikeRecommendedTestsChoiceValue::description() const
{
    return "Run tests considered by the package to be recommended";
}

ChoiceOrigin
ELikeRecommendedTestsChoiceValue::origin() const
{
    return co_special;
}

const std::string
ELikeRecommendedTestsChoiceValue::parameter() const
{
    return "";
}

const std::shared_ptr<const PermittedChoiceValueParameterValues>
ELikeRecommendedTestsChoiceValue::permitted_parameter_values() const
{
    return nullptr;
}

bool
ELikeRecommendedTestsChoiceValue::presumed() const
{
    return false;
}

const ChoicePrefixName
paludis::canonical_build_options_prefix()
{
    return ChoicePrefixName("build_options");
}

const std::string
paludis::canonical_build_options_raw_name()
{
    return "build_options";
}

const std::string
paludis::canonical_build_options_human_name()
{
    return "Build Options";
}

const UnprefixedChoiceName
ELikeExpensiveTestsChoiceValue::canonical_unprefixed_name()
{
    return UnprefixedChoiceName("expensive_tests");
}

const ChoiceNameWithPrefix
ELikeExpensiveTestsChoiceValue::canonical_name_with_prefix()
{
    return ChoiceNameWithPrefix(stringify(canonical_build_options_prefix()) + ":" + stringify(canonical_unprefixed_name()));
}

ELikeExpensiveTestsChoiceValue::ELikeExpensiveTestsChoiceValue(const std::shared_ptr<const PackageID> & id,
        const Environment * const env, const std::shared_ptr<const Choice> & choice,
        const bool l) :
    _enabled((! l) && (env->want_choice_enabled(id, choice, canonical_unprefixed_name()).is_true())),
    _mask(l)
{
}

const UnprefixedChoiceName
ELikeExpensiveTestsChoiceValue::unprefixed_name() const
{
    return canonical_unprefixed_name();
}

const ChoiceNameWithPrefix
ELikeExpensiveTestsChoiceValue::name_with_prefix() const
{
    return canonical_name_with_prefix();
}

bool
ELikeExpensiveTestsChoiceValue::enabled() const
{
    return _enabled;
}

bool
ELikeExpensiveTestsChoiceValue::enabled_by_default() const
{
    return false;
}

bool
ELikeExpensiveTestsChoiceValue::locked() const
{
    return _mask;
}

const std::string
ELikeExpensiveTestsChoiceValue::description() const
{
    return "Run tests considered by the package to be useful, but expensive";
}

ChoiceOrigin
ELikeExpensiveTestsChoiceValue::origin() const
{
    return co_special;
}

const std::string
ELikeExpensiveTestsChoiceValue::parameter() const
{
    return "";
}

const std::shared_ptr<const PermittedChoiceValueParameterValues>
ELikeExpensiveTestsChoiceValue::permitted_parameter_values() const
{
    return nullptr;
}

bool
ELikeExpensiveTestsChoiceValue::presumed() const
{
    return false;
}

const UnprefixedChoiceName
ELikeJobsChoiceValue::canonical_unprefixed_name()
{
    return UnprefixedChoiceName("jobs");
}

const ChoiceNameWithPrefix
ELikeJobsChoiceValue::canonical_name_with_prefix()
{
    return ChoiceNameWithPrefix(stringify(canonical_build_options_prefix()) + ":" + stringify(canonical_unprefixed_name()));
}

namespace
{
    std::string get_jobs(const std::shared_ptr<const PackageID> & id,
            const std::string & env_value)
    {
        if (env_value.empty())
            return "1";

        try
        {
            return stringify(destringify<unsigned>(env_value));
        }
        catch (const DestringifyError &)
        {
            Context context("When getting value of the jobs option for '" + stringify(*id) + "':");
            Log::get_instance()->message("elike_jobs_choice_value.invalid", ll_warning, lc_context)
                << "Value '" << env_value << "' is not an unsigned integer, using \"1\" instead";
            return "1";
        }
    }
}

ELikeJobsChoiceValue::ELikeJobsChoiceValue(const std::shared_ptr<const PackageID> & id,
        const Environment * const env, const std::shared_ptr<const Choice> & choice) :
    _enabled(env->want_choice_enabled(id, choice, canonical_unprefixed_name()).is_true()),
    _parameter(get_jobs(id, env->value_for_choice_parameter(id, choice, canonical_unprefixed_name())))
{
}

const UnprefixedChoiceName
ELikeJobsChoiceValue::unprefixed_name() const
{
    return canonical_unprefixed_name();
}

const ChoiceNameWithPrefix
ELikeJobsChoiceValue::name_with_prefix() const
{
    return canonical_name_with_prefix();
}

bool
ELikeJobsChoiceValue::enabled() const
{
    return _enabled;
}

bool
ELikeJobsChoiceValue::enabled_by_default() const
{
    return false;
}

bool
ELikeJobsChoiceValue::locked() const
{
    return false;
}

const std::string
ELikeJobsChoiceValue::description() const
{
    return "How many jobs the package's build system should use, where supported";
}

ChoiceOrigin
ELikeJobsChoiceValue::origin() const
{
    return co_special;
}

const std::string
ELikeJobsChoiceValue::parameter() const
{
    return _parameter;
}

const std::shared_ptr<const PermittedChoiceValueParameterValues>
ELikeJobsChoiceValue::permitted_parameter_values() const
{
    return CommonValues::get_instance()->permitted_jobs_values;
}

bool
ELikeJobsChoiceValue::presumed() const
{
    return false;
}

const UnprefixedChoiceName
ELikeTraceChoiceValue::canonical_unprefixed_name()
{
    return UnprefixedChoiceName("trace");
}

const ChoiceNameWithPrefix
ELikeTraceChoiceValue::canonical_name_with_prefix()
{
    return ChoiceNameWithPrefix(stringify(canonical_build_options_prefix()) + ":" +
            stringify(canonical_unprefixed_name()));
}

ELikeTraceChoiceValue::ELikeTraceChoiceValue(const std::shared_ptr<const PackageID> & id,
        const Environment * const env, const std::shared_ptr<const Choice> & choice) :
    _enabled(env->want_choice_enabled(id, choice, canonical_unprefixed_name()).is_true())
{
}

const UnprefixedChoiceName
ELikeTraceChoiceValue::unprefixed_name() const
{
    return canonical_unprefixed_name();
}

const ChoiceNameWithPrefix
ELikeTraceChoiceValue::name_with_prefix() const
{
    return canonical_name_with_prefix();
}

bool
ELikeTraceChoiceValue::enabled() const
{
    return _enabled;
}

bool
ELikeTraceChoiceValue::enabled_by_default() const
{
    return false;
}

bool
ELikeTraceChoiceValue::locked() const
{
    return false;
}

const std::string
ELikeTraceChoiceValue::description() const
{
    return "Trace actions executed by the package (very noisy, for debugging broken builds only)";
}

ChoiceOrigin
ELikeTraceChoiceValue::origin() const
{
    return co_special;
}

const std::string
ELikeTraceChoiceValue::parameter() const
{
    return "";
}

const std::shared_ptr<const PermittedChoiceValueParameterValues>
ELikeTraceChoiceValue::permitted_parameter_values() const
{
    return nullptr;
}

bool
ELikeTraceChoiceValue::presumed() const
{
    return false;
}

namespace
{
    ELikeSymbolsChoiceValueParameter get_symbols(const std::shared_ptr<const PackageID> & id,
            const std::string & env_value)
    {
        if (env_value.empty())
            return escvp_split;

        try
        {
            return destringify<ELikeSymbolsChoiceValueParameter>(env_value);
        }
        catch (const DestringifyError &)
        {
            Context context("When getting value of the symbols option for '" + stringify(*id) + "':");
            Log::get_instance()->message("elike_symbols_choice_value.invalid", ll_warning, lc_context)
                << "Value '" << env_value << "' is not a legal value, using \"split\" instead";
            return escvp_split;
        }
    }
}

ELikeSymbolsChoiceValue::ELikeSymbolsChoiceValue(const std::shared_ptr<const PackageID> & id,
        const Environment * const env, const std::shared_ptr<const Choice> & choice, const ELikeSymbolsChoiceValueParameter _force) :
    _enabled(! env->want_choice_enabled(id, choice, canonical_unprefixed_name()).is_false()),
    _param(_force != last_escvp ? _force : get_symbols(id, env->value_for_choice_parameter(id, choice, canonical_unprefixed_name())))
{
}

const UnprefixedChoiceName
ELikeSymbolsChoiceValue::unprefixed_name() const
{
    return canonical_unprefixed_name();
}

const ChoiceNameWithPrefix
ELikeSymbolsChoiceValue::name_with_prefix() const
{
    return canonical_name_with_prefix();
}

bool
ELikeSymbolsChoiceValue::enabled() const
{
    return _enabled;
}

bool
ELikeSymbolsChoiceValue::enabled_by_default() const
{
    return true;
}

bool
ELikeSymbolsChoiceValue::locked() const
{
    return false;
}

const std::string
ELikeSymbolsChoiceValue::description() const
{
    return "How to handle debug symbols in installed files";
}

ChoiceOrigin
ELikeSymbolsChoiceValue::origin() const
{
    return co_special;
}

const std::string
ELikeSymbolsChoiceValue::parameter() const
{
    return stringify(_param);
}

const std::shared_ptr<const PermittedChoiceValueParameterValues>
ELikeSymbolsChoiceValue::permitted_parameter_values() const
{
    return CommonValues::get_instance()->permitted_symbols_values;
}

bool
ELikeSymbolsChoiceValue::presumed() const
{
    return false;
}

const UnprefixedChoiceName
ELikeSymbolsChoiceValue::canonical_unprefixed_name()
{
    return UnprefixedChoiceName("symbols");
}

const ChoiceNameWithPrefix
ELikeSymbolsChoiceValue::canonical_name_with_prefix()
{
    return ChoiceNameWithPrefix(stringify(canonical_build_options_prefix()) + ":" + stringify(canonical_unprefixed_name()));
}

bool
ELikeSymbolsChoiceValue::should_split(const std::string & v)
{
    switch (destringify<ELikeSymbolsChoiceValueParameter>(v))
    {
        case escvp_split:
        case escvp_compress:
            return true;

        case escvp_preserve:
        case escvp_strip:
            return false;

        case last_escvp:
            break;
    }

    throw InternalError(PALUDIS_HERE, "Unhandled ELikeSymbolsChoiceValueParameter");
}

bool
ELikeSymbolsChoiceValue::should_strip(const std::string & v)
{
    switch (destringify<ELikeSymbolsChoiceValueParameter>(v))
    {
        case escvp_split:
        case escvp_compress:
        case escvp_strip:
            return true;

        case escvp_preserve:
            return false;

        case last_escvp:
            break;
    }

    throw InternalError(PALUDIS_HERE, "Unhandled ELikeSymbolsChoiceValueParameter");
}

bool
ELikeSymbolsChoiceValue::should_compress(const std::string & v)
{
    switch (destringify<ELikeSymbolsChoiceValueParameter>(v))
    {
        case escvp_compress:
            return true;

        case escvp_split:
        case escvp_preserve:
        case escvp_strip:
            return false;

        case last_escvp:
            break;
    }

    throw InternalError(PALUDIS_HERE, "Unhandled ELikeSymbolsChoiceValueParameter");
}

namespace
{
    ELikeWorkChoiceValueParameter get_work(const std::shared_ptr<const PackageID> & id,
            const std::string & env_value)
    {
        if (env_value.empty())
            return ewcvp_tidyup;

        try
        {
            return destringify<ELikeWorkChoiceValueParameter>(env_value);
        }
        catch (const DestringifyError &)
        {
            Context context("When getting value of the symbols option for '" + stringify(*id) + "':");
            Log::get_instance()->message("elike_work_choice_value.invalid", ll_warning, lc_context)
                << "Value '" << env_value << "' is not a legal value, using \"tidyup\" instead";
            return ewcvp_tidyup;
        }
    }
}

ELikeWorkChoiceValue::ELikeWorkChoiceValue(const std::shared_ptr<const PackageID> & id,
        const Environment * const env, const std::shared_ptr<const Choice> & choice, const ELikeWorkChoiceValueParameter _force) :
    _enabled(! env->want_choice_enabled(id, choice, canonical_unprefixed_name()).is_false()),
    _param(_force != last_ewcvp ? _force : get_work(id, env->value_for_choice_parameter(id, choice, canonical_unprefixed_name())))
{
}

const UnprefixedChoiceName
ELikeWorkChoiceValue::unprefixed_name() const
{
    return canonical_unprefixed_name();
}

const ChoiceNameWithPrefix
ELikeWorkChoiceValue::name_with_prefix() const
{
    return canonical_name_with_prefix();
}

bool
ELikeWorkChoiceValue::enabled() const
{
    return _enabled;
}

bool
ELikeWorkChoiceValue::enabled_by_default() const
{
    return true;
}

bool
ELikeWorkChoiceValue::locked() const
{
    return false;
}

const std::string
ELikeWorkChoiceValue::description() const
{
    return "Whether to preserve or remove working directories";
}

ChoiceOrigin
ELikeWorkChoiceValue::origin() const
{
    return co_special;
}

const std::string
ELikeWorkChoiceValue::parameter() const
{
    return stringify(_param);
}

const std::shared_ptr<const PermittedChoiceValueParameterValues>
ELikeWorkChoiceValue::permitted_parameter_values() const
{
    return CommonValues::get_instance()->permitted_work_values;
}

bool
ELikeWorkChoiceValue::presumed() const
{
    return false;
}

const UnprefixedChoiceName
ELikeWorkChoiceValue::canonical_unprefixed_name()
{
    return UnprefixedChoiceName("work");
}

const ChoiceNameWithPrefix
ELikeWorkChoiceValue::canonical_name_with_prefix()
{
    return ChoiceNameWithPrefix(stringify(canonical_build_options_prefix()) + ":" + stringify(canonical_unprefixed_name()));
}

bool
ELikeWorkChoiceValue::should_remove(const std::string & v)
{
    switch (destringify<ELikeWorkChoiceValueParameter>(v))
    {
        case ewcvp_tidyup:
        case ewcvp_remove:
            return true;

        case ewcvp_preserve:
        case ewcvp_leave:
            return false;

        case last_ewcvp:
            break;
    }

    throw InternalError(PALUDIS_HERE, "Unhandled ELikeWorkChoiceValueParameter");
}

bool
ELikeWorkChoiceValue::should_merge_nondestructively(const std::string & v)
{
    switch (destringify<ELikeWorkChoiceValueParameter>(v))
    {
        case ewcvp_preserve:
            return true;

        case ewcvp_tidyup:
        case ewcvp_remove:
        case ewcvp_leave:
            return false;

        case last_ewcvp:
            break;
    }

    throw InternalError(PALUDIS_HERE, "Unhandled ELikeWorkChoiceValueParameter");
}

bool
ELikeWorkChoiceValue::should_remove_on_failure(const std::string & v)
{
    switch (destringify<ELikeWorkChoiceValueParameter>(v))
    {
        case ewcvp_remove:
            return true;

        case ewcvp_tidyup:
        case ewcvp_preserve:
        case ewcvp_leave:
            return false;

        case last_ewcvp:
            break;
    }

    throw InternalError(PALUDIS_HERE, "Unhandled ELikeWorkChoiceValueParameter");
}

// ELikeDwarfCompressionChoiceValue

const UnprefixedChoiceName
ELikeDwarfCompressionChoiceValue::canonical_unprefixed_name()
{
    return UnprefixedChoiceName("dwarf_compress");
}

const ChoiceNameWithPrefix
ELikeDwarfCompressionChoiceValue::canonical_name_with_prefix()
{
    return ChoiceNameWithPrefix(stringify(canonical_build_options_prefix())
                                + ":" +
                                stringify(canonical_unprefixed_name()));
}

ELikeDwarfCompressionChoiceValue::ELikeDwarfCompressionChoiceValue(const std::shared_ptr<const PackageID> & id,
                                                                   const Environment * const env,
                                                                   const std::shared_ptr<const Choice> & choice)
    : _enabled(env->want_choice_enabled(id, choice, canonical_unprefixed_name()).is_true())
{
}

const UnprefixedChoiceName
ELikeDwarfCompressionChoiceValue::unprefixed_name() const
{
    return canonical_unprefixed_name();
}

const ChoiceNameWithPrefix
ELikeDwarfCompressionChoiceValue::name_with_prefix() const
{
    return canonical_name_with_prefix();
}

bool
ELikeDwarfCompressionChoiceValue::enabled() const
{
    return _enabled;
}

bool
ELikeDwarfCompressionChoiceValue::enabled_by_default() const
{
    return false;
}

bool
ELikeDwarfCompressionChoiceValue::presumed() const
{
    return false;
}

bool
ELikeDwarfCompressionChoiceValue::locked() const
{
    return false;
}

const std::string
ELikeDwarfCompressionChoiceValue::description() const
{
    return "Compress DWARF2+ debug information";
}

ChoiceOrigin
ELikeDwarfCompressionChoiceValue::origin() const
{
    return co_special;
}

const std::string
ELikeDwarfCompressionChoiceValue::parameter() const
{
    return "";
}

const std::shared_ptr<const PermittedChoiceValueParameterValues>
ELikeDwarfCompressionChoiceValue::permitted_parameter_values() const
{
    return nullptr;
}