/* Copyright (c) 2012 Massachusetts Institute of Technology
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#include "model/Model.h"

#include <vector>

#include "util/Result.h"

namespace DSENT
{
    using std::vector;
    using LibUtil::deletePtrMap;
    using LibUtil::clonePtrMap;

    Model::SubModel::SubModel(Model* model_, double num_models_)
        : m_model_(model_), m_num_models_(num_models_)
    {}

    Model::SubModel::~SubModel()
    {
        delete m_model_;
    }

    Model* Model::SubModel::getModel()
    {
        return m_model_;
    }

    const Model* Model::SubModel::getModel() const
    {
        return m_model_;
    }

    double Model::SubModel::getNumModels() const
    {
        return m_num_models_;
    }

    Model::SubModel* Model::SubModel::clone() const
    {
        return new SubModel(*this);
    }

    Model::SubModel::SubModel(const SubModel& sub_model_)
    {
        m_model_ = sub_model_.m_model_->clone();
        m_num_models_ = sub_model_.m_num_models_;
    }

    const char Model::TYPE_SEPARATOR[] = ">>";
    const char Model::HIERARCHY_SEPARATOR[] = "->";
    const char Model::SUBFIELD_SEPARATOR[] = ":";
    const char Model::DETAIL_SEPARATOR[] = "@";

    Model::Model(const String& instance_name_, const TechModel* tech_model_)
        : m_instance_name_(instance_name_), m_tech_model_(tech_model_),
            m_constructed_(false), m_updated_(false), m_evaluated_(false)
    {
        m_property_names_ = new vector<String>;
        m_parameter_names_ = new vector<String>;
        m_parameters_ = new ParameterMap();        
        m_properties_ = new PropertyMap();
        m_generated_properties_ = new PropertyMap();
        m_sub_instances_ = new Map<SubModel*>();
        m_event_map_ = new Map<Result*>();
        m_area_map_ = new Map<Result*>();
        m_ndd_power_map_ = new Map<Result*>();
    }

    Model::~Model()
    {
        // Clear parameter names
        delete m_parameter_names_;
        // Clear property name
        delete m_property_names_;

        // Clear parameters
        delete m_parameters_;
        m_parameters_ = NULL;
        // Clear input properties
        delete m_properties_;
        m_properties_ = NULL;

        // Clear generated properties
        delete m_generated_properties_;
        m_generated_properties_ = NULL;

        // Clear sub models
        deletePtrMap<SubModel>(m_sub_instances_);
        m_sub_instances_ = NULL;

        // Clear all results
        deletePtrMap<Result>(m_event_map_);
        m_event_map_ = NULL;
        deletePtrMap<Result>(m_area_map_);
        m_area_map_ = NULL;
        deletePtrMap<Result>(m_ndd_power_map_);
        m_ndd_power_map_ = NULL;
    }

    void Model::setInstanceName(const String& instance_name_)
    {
        m_instance_name_ = instance_name_;
        return;
    }

    const String& Model::getInstanceName() const
    {
        return m_instance_name_;
    }

    void Model::setIsTopModel(bool is_top_model_)
    {
        m_is_top_model_ = is_top_model_;
        return;
    }

    bool Model::getIsTopModel() const
    {
        return m_is_top_model_;
    }

    //-------------------------------------------------------------------------
    //  Parameters and properties checks
    //-------------------------------------------------------------------------
    void Model::addParameterName(const String& parameter_name_)
    {
        ASSERT(!m_constructed_, "[Error] " + getInstanceName() +
            " -> Cannot add additional parameters names after model is constructed!");
        m_parameter_names_->push_back(parameter_name_);

        return;
    }

    void Model::addParameterName(const String& parameter_name_, const String& parameter_default_)
    {
        ASSERT(!m_constructed_, "[Error] " + getInstanceName() +
            " -> Cannot add additional parameters names after model is constructed!");
        m_parameter_names_->push_back(parameter_name_);
        setParameter(parameter_name_, parameter_default_);
        return;
    }
    
    const vector<String>* Model::getParameterNames() const
    {
        return m_parameter_names_;
    }

    void Model::addPropertyName(const String& property_name_)
    {
        ASSERT(!m_constructed_, "[Error] " + getInstanceName() +
            " -> Cannot add additional property names after model is constructed!");
        m_property_names_->push_back(property_name_);
        return;
    }

    void Model::addPropertyName(const String& property_name_, const String& property_default_)
    {
        ASSERT(!m_constructed_, "[Error] " + getInstanceName() +
            " -> Cannot add additional property names after model is constructed!");
        m_property_names_->push_back(property_name_);
        setProperty(property_name_, property_default_);
        return;
    }

    const vector<String>* Model::getPropertyNames() const
    {
        return m_property_names_;
    }

    void Model::checkParameters() const
    {
        String missing_parameters = "";
        
        for(int i = 0; i < (int)m_parameter_names_->size(); ++i)
        {
            const String& parameter_name = m_parameter_names_->at(i);
            if (!m_parameters_->keyExist(parameter_name))
                missing_parameters += "    " + parameter_name + "\n";
        }
        
        ASSERT(missing_parameters.size() == 0, "[Error] " + m_instance_name_ + 
                " -> Missing parameters:\n" + missing_parameters);
        return;
    }

    void Model::checkProperties() const
    {
        String missing_properties = "";
        
        for(int i = 0; i < (int)m_property_names_->size(); ++i)
        {
            const String& property_name = m_property_names_->at(i);
            if (!m_properties_->keyExist(property_name))
                missing_properties += "    " + property_name + "\n";
        }
        
        ASSERT(missing_properties.size() == 0, "[Error] " + m_instance_name_ + 
                " -> Missing properties:\n" + missing_properties);
        return;
    }
    //-------------------------------------------------------------------------

    //-------------------------------------------------------------------------
    //  Parameters Manipulation
    //-------------------------------------------------------------------------
    const ParameterMap* Model::getParameters() const
    {
        return m_parameters_;
    }

    const String Model::getParameter(const String& parameter_name_) const
    {
        return m_parameters_->get(parameter_name_);
    }
    
    void Model::setParameter(const String& parameter_name_, const String& parameter_value_)
    {
        ASSERT(!m_constructed_, "[Error] " + getInstanceName() +
            " -> Cannot set parameters after model is constructed!");
        m_parameters_->set(parameter_name_, parameter_value_);
    }
    //-------------------------------------------------------------------------
    
    //-------------------------------------------------------------------------
    // Properties Manipulation
    //-------------------------------------------------------------------------
    const PropertyMap* Model::getProperties() const
    {
        return m_properties_;
    }

    const String Model::getProperty(const String& property_name_) const
    {
        return m_properties_->get(property_name_);
    }
    
    void Model::setProperty(const String& property_name_, const String& property_value_)
    {
        // If any properties changed, reset updated and evaluated flags
        m_updated_ = false;
        m_evaluated_ = false;
        m_properties_->set(property_name_, property_value_);
    }
    //-------------------------------------------------------------------------
            
    PropertyMap* Model::getGenProperties()
    {
        return m_generated_properties_;
    }

    const PropertyMap* Model::getGenProperties() const
    {
        return m_generated_properties_;
    }

    void Model::addSubInstances(Model* sub_instance_, double num_sub_instances_)
    {
        // Get instance name
        const String& sub_instance_name = sub_instance_->getInstanceName();

        // Check if the instance exists
        if(m_sub_instances_->keyExist(sub_instance_name))
        {
            const String& error_msg = "[Error] " + m_instance_name_ + 
            " -> Instance exists (" + sub_instance_name + ")";
            throw Exception(error_msg);
        }

        // Check if the num_sub_instances_ is a positive number
        ASSERT((num_sub_instances_ >= 0), "[Error] " + m_instance_name_ + 
        " -> Invalid number of instance (" + String(num_sub_instances_) + ")");

        // Add the instance
        m_sub_instances_->set(sub_instance_name, new SubModel(sub_instance_, num_sub_instances_));
        return;
    }

    Model* Model::getSubInstance(const String& sub_instance_name_)
    {
        // Throw an Exception if the instance already exists
        if(!m_sub_instances_->keyExist(sub_instance_name_))
        {
            const String& error_msg = "[Error] " + m_instance_name_ + 
            " -> Instance not exists (" + sub_instance_name_ + ")";
            throw Exception(error_msg);
        }

        return m_sub_instances_->get(sub_instance_name_)->getModel();
    }

    const Model* Model::getSubInstance(const String& sub_instance_name_) const
    {
        // Throw an Exception if the instance does not exist
        if(!m_sub_instances_->keyExist(sub_instance_name_))
        {
            const String& error_msg = "[Error] " + m_instance_name_ + 
            " -> Instance not exists (" + sub_instance_name_ + ")";
            throw Exception(error_msg);
        }

        return m_sub_instances_->get(sub_instance_name_)->getModel();
    }

    bool Model::hasSubInstance(const String& sub_instance_name_) const
    {
        return m_sub_instances_->keyExist(sub_instance_name_);
    }

    void Model::addAreaResult(Result* area_)
    {
        const String& area_name = area_->getName();

        // Throw an Exception if the area already exists
        if(m_area_map_->keyExist(area_name))
        {
            const String& error_msg = "Internal error: area (" + area_name + 
                ") exists";
            throw Exception(error_msg);
        }

        // Add the area
        m_area_map_->set(area_name, area_);
        return;
    }

    Result* Model::getAreaResult(const String& area_name_)
    {
        return m_area_map_->get(area_name_);
    }

    const Result* Model::getAreaResult(const String& area_name_) const
    {
        return m_area_map_->get(area_name_);
    }

    bool Model::hasAreaResult(const String& area_name_) const
    {
        return m_area_map_->keyExist(area_name_);
    }

    void Model::addNddPowerResult(Result* ndd_power_)
    {
        const String& ndd_power_name = ndd_power_->getName();

        // Throw an Exception if the ndd_power already exists
        if(m_ndd_power_map_->keyExist(ndd_power_name))
        {
            const String& error_msg = "Internal error: ndd_power (" + ndd_power_name + 
                ") exists";
            throw Exception(error_msg);
        }

        // Add the ndd_power
        m_ndd_power_map_->set(ndd_power_name, ndd_power_);
        return;
    }

    Result* Model::getNddPowerResult(const String& ndd_power_name_)
    {
        return m_ndd_power_map_->get(ndd_power_name_);
    }

    const Result* Model::getNddPowerResult(const String& ndd_power_name_) const
    {
        return m_ndd_power_map_->get(ndd_power_name_);
    }

    bool Model::hasNddPowerResult(const String& ndd_power_name_) const
    {
        return m_ndd_power_map_->keyExist(ndd_power_name_);
    }

    void Model::addEventResult(Result* event_)
    {
        const String& event_name = event_->getName();

        // Throw an Exception if the event already exists
        if(m_event_map_->keyExist(event_name))
        {
            const String& error_msg = "Internal error: event (" + event_name + 
                ") exists";
            throw Exception(error_msg);
        }

        // Add the event
        m_event_map_->set(event_name, event_);
        return;
    }

    Result* Model::getEventResult(const String& event_name_)
    {
        return m_event_map_->get(event_name_);
    }

    const Result* Model::getEventResult(const String& event_name_) const
    {
        return m_event_map_->get(event_name_);
    }

    bool Model::hasEventResult(const String& event_name_) const
    {
        return m_event_map_->keyExist(event_name_);
    }

    const TechModel* Model::getTechModel() const
    {
        return m_tech_model_;
    }

    const void* Model::parseQuery(const String& query_type_, const String& query_hier_, const String& query_sub_field_)
    {
        // Break query by hierarchy separator
        vector<String> hier_split = query_hier_.splitByString(HIERARCHY_SEPARATOR);

        // Check if the query_hier matches the instance name
        ASSERT((hier_split[0] == m_instance_name_), "[Error] " + 
                m_instance_name_ + " -> Mismatch in instance name (" + 
                hier_split[0] + ")");

        // If there is no more hierarchy separator, this process the query
        if(hier_split.size() == 1)
        {
            // Query the model
            return processQuery(query_type_, query_sub_field_);
        }
        else
        {
            // Reconstruct the query
            String temp_query_hier = hier_split[1];
            for(int i = 2; i < (int)hier_split.size(); ++i)
            {
                temp_query_hier += HIERARCHY_SEPARATOR + hier_split[i];
            }

            // Get sub instance's name
            const String& temp_sub_instance_name = hier_split[1];
            ASSERT(m_sub_instances_->keyExist(temp_sub_instance_name), "[Error] " + 
                    m_instance_name_ + " -> No sub-instances queried (" + 
                    temp_sub_instance_name + ")");

            return m_sub_instances_->get(temp_sub_instance_name)->getModel()->parseQuery(query_type_, temp_query_hier, query_sub_field_);
        }
    }

    const void* Model::processQuery(const String& query_type_, const String& query_sub_field_)
    {
        if(query_type_ == "Property")
        {
            return getProperties();
        }
        else if(query_type_ == "Parameter")
        {
            return getParameters();
        }
        else if(query_type_.contain("Hier"))
        {
            return this;
        }
        else if(query_type_ == "Area")
        {
            return queryArea(query_sub_field_);
        }
        else if(query_type_ == "NddPower")
        {
            return queryNddPower(query_sub_field_);
        }
        else if(query_type_ == "Energy")
        {
            return queryEventEnergyCost(query_sub_field_);
        }
        else 
        {
            const String& error_msg = "[Error] " + m_instance_name_ + " -> Unknown query type (" + query_type_ + ")";
            throw Exception(error_msg);
            return NULL;
        }
    }

    const Result* Model::queryArea(const String& area_name_) const
    {
        ASSERT(m_area_map_->keyExist(area_name_), "[Error] " + m_instance_name_ + 
                " -> Unknown queried area name (" + area_name_ + ")");
        return m_area_map_->get(area_name_);
    }

    const Result* Model::queryNddPower(const String& ndd_power_name_)
    {
        ASSERT(m_ndd_power_map_->keyExist(ndd_power_name_), "[Error] " + m_instance_name_ + 
                " -> Unknown queried ndd power name (" + ndd_power_name_ + ")");

        use("Idle");
        return m_ndd_power_map_->get(ndd_power_name_);
    }

    const Result* Model::queryEventEnergyCost(const String& event_name_)
    {
        ASSERT(m_event_map_->keyExist(event_name_), "[Error] " + m_instance_name_ + 
                " -> Unknown queried event name (" + event_name_ + ")");

        use(event_name_);
        return m_event_map_->get(event_name_);
    }

    // Update checks whether the model needs updating, whether all properties have been specified,
    // and calls updateModel if update is necessary
    void Model::construct()
    {
        // Model should not be constructed yet
        ASSERT(!m_constructed_, "[Error] " + getInstanceName() + " -> Cannot construct an already contructed model!");
        // Check if whether all needed parameters are defined
        checkParameters();
        constructModel();
        m_constructed_ = true;
        m_updated_ = false;
        m_evaluated_ = false;        
        return;
    }
    
    // Update checks whether the model needs updating, whether all properties have been specified,
    // and calls updateModel if update is necessary
    void Model::update()
    {
        // Model should be constructed
        ASSERT(m_constructed_, "[Error] " + getInstanceName() + " -> Cannot update an unconstructed model!");
        // If the model needs updating (due to property change)
        // an update is necessary
        if (!m_updated_)
        {
            // Check if all properties needed exist
            checkProperties();
            updateModel();
            m_updated_ = true;
            m_evaluated_ = false;
        }        
        return;
    }

    // Evaluate checks whether the model needs to be evaluated. 
    void Model::evaluate()
    {
        // Model should be constructed
        ASSERT(m_constructed_, "[Error] " + getInstanceName() + " -> Cannot evaluate an unconstructed model!");
        // Model should be updated
        ASSERT(m_updated_, "[Error] " + getInstanceName() + " -> Cannot evaluate without first updating!");
        // If the model needs evaluating
        if (!m_evaluated_)
        {
            evaluateModel();
            m_evaluated_ = true;
        }

        return;
    }

    void Model::use(const String& event_name_)
    {
        useModel(event_name_);
        return;
    }
    
    void Model::use()
    {
        useModel();
        return;
    }

    // By default, update model will iterate through all sub-instances and do updateModel on them
    void Model::updateModel()
    {
        Map<SubModel*>::Iterator iter = m_sub_instances_->begin();
        Map<SubModel*>::Iterator end = m_sub_instances_->end();
        while (iter != end)
        {
            iter->second->getModel()->update();
            iter++;
        }        
        return;
    }

    // By default, update model will iterate through all sub-instances and do updateModel on them
    void Model::evaluateModel()
    {
        Map<SubModel*>::Iterator iter = m_sub_instances_->begin();
        Map<SubModel*>::Iterator end = m_sub_instances_->end();
        while (iter != end)
        {
            iter->second->getModel()->evaluate();
            iter++;
        }        
        return;
    }

    void Model::useModel(const String& /* event_name_ */)
    {}

    void Model::useModel()
    {}

    void Model::printHierarchy(const String& query_type_, const String& query_sub_field_, const String& prepend_str_, int detail_level_, ostream& ost_) const
    {
        if(query_type_ == "InstHier")
        {
            ost_ << prepend_str_ << getInstanceName() << endl;
            printInstHierarchy(prepend_str_, detail_level_, ost_);
            //if(detail_level_ > 0)
            //{
                //for(Map<SubModel*>::ConstIterator it = m_sub_instances_->begin(); it != m_sub_instances_->end(); ++it)
                //{
                    //const Model* sub_model = (it->second)->getModel();
                    //String temp_prepend_str = prepend_str_ + "    ";
                    //sub_model->printHierarchy(query_type_, query_sub_field_, temp_prepend_str, detail_level_ - 1, ost_);
                //}
            //}
        }
        else
        {
            const Map<Result*>* result_map;

            if(query_type_ == "AreaHier")
            {
                result_map = m_area_map_;
            }
            else if(query_type_ == "NddPowerHier")
            {
                result_map = m_ndd_power_map_;
            }
            else if(query_type_ == "EventHier")
            {
                result_map = m_event_map_;
            }
            else
            {
                const String& error_msg = "[Error] " + m_instance_name_ + " -> Unknown query type (" + query_type_ + ")";
                throw Exception(error_msg);
                return;
            }

            if(query_sub_field_ == "")
            {
                for(Map<Result*>::ConstIterator it = result_map->begin(); it != result_map->end(); ++it)
                {
                    const Result* result = it->second;
                    ost_ << prepend_str_ << getInstanceName() << "->" << result->getName() << endl;
                    result->printHierarchy(prepend_str_, detail_level_, ost_);
                }
            }
            else
            {
                const Result* result = result_map->get(query_sub_field_);
                ost_ << prepend_str_ << getInstanceName() << "->" << result->getName() << endl;
                result->printHierarchy(prepend_str_, detail_level_, ost_);
            }
        }
        return;
    }

    void Model::printInstHierarchy(const String& prepend_str_, int detail_level_, ostream& ost_) const
    {
        if(detail_level_ > 0)
        {
            for(Map<SubModel*>::ConstIterator it = m_sub_instances_->begin(); it != m_sub_instances_->end(); ++it)
            {
                const Model* sub_model = it->second->getModel();
                String temp_prepend_str = prepend_str_ + "    ";

                ost_ << prepend_str_ << " |--" << sub_model->getInstanceName() << endl;
                sub_model->printInstHierarchy(temp_prepend_str, detail_level_ - 1, ost_);
            }
        }
        return;
    }

    Model* Model::clone() const
    {
        throw Exception(getInstanceName() + " -> Cannot be cloned!");
    }
    
    Model::Model(const Model& model_)
    {
        // Copy instance's name
        m_instance_name_ = model_.m_instance_name_;

        // Clone properties
        m_properties_ = model_.m_properties_->clone();

        // Clone instances
        m_sub_instances_ = clonePtrMap(model_.m_sub_instances_);

        // Clone events, area, ndd_power
        m_event_map_ = clonePtrMap(model_.m_event_map_);
        m_area_map_ = clonePtrMap(model_.m_area_map_);
        m_ndd_power_map_ = clonePtrMap(model_.m_ndd_power_map_);

        // Copy tech model pointer
        m_tech_model_ = model_.m_tech_model_;
    }

} // namespace DSENT