#ifndef ARGPARSE_VALUE_HPP
#define ARGPARSE_VALUE_HPP
#include <iostream>
#include "argparse_error.hpp"

namespace argparse {

    template<class T>
    class ConvertedValue {
        public:
            typedef T value_type;
        public:
            void set_value(T val) { errored_ = false; value_ = val; }
            void set_error(std::string msg) { errored_ = true; error_msg_ = msg; }

            T value() const { return value_; }
            std::string error() const { return error_msg_; }

            operator bool() { return valid(); }
            bool valid() const { return !errored_; }
        private:
            T value_ = T();
            std::string error_msg_;
            bool errored_ = true;
    };

    //How the value associated with an argumetn was initialized
    enum class Provenance {
        UNSPECIFIED,//The value was default constructed
        DEFAULT,    //The value was set by a default (e.g. as a command-line argument default value)
        SPECIFIED,  //The value was explicitly specified (e.g. explicitly specified on the command-line)
        INFERRED,   //The value was inferred, or conditionally set based on other values
    };

    /*
     * ArgValue represents the 'value' of a command-line option/argument
     *
     * It supports implicit conversion to the underlying value_type, which means it can
     * be seamlessly used as the value_type in most situations.
     *
     * It additionally tracks the provenance off the option, along with it's associated argument group.
     */
    template<typename T>
    class ArgValue {
        public:
            typedef T value_type;

        public: //Accessors
            //Automatic conversion to underlying value type
            operator T() const { return value_; }

            //Returns the value assoicated with this argument
            const T& value() const { return value_; }

            //Returns the provenance of this argument (i.e. how it was initialized)
            Provenance provenance() const { return provenance_; }

            //Returns the group this argument is associated with (or an empty string if none)
            const std::string& argument_group() const { return argument_group_; }

            const std::string& argument_name() const { return argument_name_; }

        public: //Mutators
            void set(ConvertedValue<T> val, Provenance prov) {
                if (!val.valid()) {
                    //If the value didn't convert properly, it should
                    //have an error message so raise it
                    throw ArgParseConversionError(val.error());
                }
                value_ = val.value();
                provenance_ = prov;
            }

            void set(T val, Provenance prov) {
                value_ = val;
                provenance_ = prov;
            }

            T& mutable_value(Provenance prov) {
                provenance_ = prov;
                return value_;
            }

            void set_argument_group(std::string grp) {
                argument_group_ = grp;
            }

            void set_argument_name(std::string name_str) {
                argument_name_ = name_str;
            }
        private:
            T value_ = T();
            Provenance provenance_ = Provenance::UNSPECIFIED;
            std::string argument_group_ = "";
            std::string argument_name_ = "";
    };

    //Automatically convert to the underlying type for ostream output
    template<typename T>
    std::ostream& operator<<(std::ostream& os, const ArgValue<T> t) {
        return os << T(t);
    }

}
#endif
