/* 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/ElectricalModel.h"

#include "model/PortInfo.h"
#include "model/EventInfo.h"
#include "model/timing_graph/ElectricalDriver.h"
#include "model/timing_graph/ElectricalDriverMultiplier.h"
#include "model/timing_graph/ElectricalNet.h"
#include "model/timing_graph/ElectricalLoad.h"
#include "model/timing_graph/ElectricalDelay.h"

namespace DSENT
{
    ElectricalModel::ElectricalModel(const String& instance_name_, const TechModel* tech_model_)
        : Model(instance_name_, tech_model_)
    {
        m_curr_driving_strengths_idx_ = -1;
        m_input_ports_ = new Map<PortInfo*>;
        m_output_ports_ = new Map<PortInfo*>;
        m_net_references_ = new Map<NetIndex>;
        m_drivers_ = new Map<ElectricalDriver*>;
        m_driver_multipliers_ = new Map<ElectricalDriverMultiplier*>;
        m_nets_ = new Map<ElectricalNet*>;
        m_loads_ = new Map<ElectricalLoad*>;
        m_delays_ = new Map<ElectricalDelay*>;
        m_event_infos_ = new Map<EventInfo*>;
    }

    ElectricalModel::~ElectricalModel()
    {
        deletePtrMap<PortInfo>(m_input_ports_);
        deletePtrMap<PortInfo>(m_output_ports_);
        delete m_net_references_;
        deletePtrMap<ElectricalDriver>(m_drivers_);
        deletePtrMap<ElectricalDriverMultiplier>(m_driver_multipliers_);
        deletePtrMap<ElectricalNet>(m_nets_);
        deletePtrMap<ElectricalLoad>(m_loads_);
        deletePtrMap<ElectricalDelay>(m_delays_);
        deletePtrMap<EventInfo>(m_event_infos_);
        m_input_ports_ = NULL;
        m_output_ports_ = NULL;
        m_net_references_ = NULL;
        m_drivers_ = NULL;
        m_driver_multipliers_ = NULL;
        m_nets_ = NULL;
        m_loads_ = NULL;
        m_net_references_ = NULL;
        m_event_infos_ = NULL;
    }

    void ElectricalModel::checkProperties() const
    {
        // Check if the specified driving strength exists in the available driving strengths
        if(getProperties()->keyExist("DrivingStrength"))
        {
            const double driving_strength = getProperty("DrivingStrength");
            bool is_found = false;
            for(int i = 0; i < (int)m_driving_strengths_.size(); ++i)
            {
                if(driving_strength == m_driving_strengths_[i])
                {
                    is_found = true;
                    break;
                }
            }
            ASSERT(is_found, "[Error] " + getInstanceName() + 
                " -> Driving strength (" + String(driving_strength) + ")"
                " not found in available driving strengths (" + 
                getParameter("AvailableDrivingStrengths"));
        }

        // Do normal check on the properties
        Model::checkProperties();
        return;
    }

    double ElectricalModel::getDrivingStrength() const
    {
        if(m_curr_driving_strengths_idx_ == -1)
        {
            return 0;
        }
        else
        {
            return m_driving_strengths_[m_curr_driving_strengths_idx_];
        }
    }

    int ElectricalModel::getDrivingStrengthIdx() const
    {
        return m_curr_driving_strengths_idx_;
    }

    void ElectricalModel::setDrivingStrengthIdx(int idx_)
    {
        ASSERT(((idx_ >= 0) && (idx_ < (int)m_driving_strengths_.size())), 
                "[Error] " + getInstanceName() + 
                " -> Driving strength index out of range (" + String(idx_) + ")");

        m_curr_driving_strengths_idx_ = idx_;
        setProperty("DrivingStrength", m_driving_strengths_[m_curr_driving_strengths_idx_]);
        
        Log::printLine(getInstanceName() + " -> Changing drive strength to " + (String) m_driving_strengths_[m_curr_driving_strengths_idx_]);
        update();
        return;
    }

    void ElectricalModel::setMinDrivingStrength()
    {
        setDrivingStrengthIdx(0);
        return;
    }

    bool ElectricalModel::hasMinDrivingStrength() const
    {
        return (m_curr_driving_strengths_idx_ == 0);
    }

    bool ElectricalModel::hasMaxDrivingStrength() const
    {
        return (m_curr_driving_strengths_idx_ == ((int)m_driving_strengths_.size() - 1));
    }

    void ElectricalModel::increaseDrivingStrength()
    {
        if(!hasMaxDrivingStrength())
        {
            setDrivingStrengthIdx(m_curr_driving_strengths_idx_ + 1);
        }
        return;
    }

    void ElectricalModel::decreaseDrivingStrength()
    {
        if(!hasMinDrivingStrength())
        {
            setDrivingStrengthIdx(m_curr_driving_strengths_idx_ - 1);
        }
        return;
    }

    void ElectricalModel::setAvailableDrivingStrengths(const String& driving_strengths_)
    {
        setParameter("AvailableDrivingStrengths", driving_strengths_);
        const vector<String>& split_str = driving_strengths_.split("[,");

        // Check if there is at least one driving strength specified
        ASSERT(!split_str.empty(), "[Error] " + getInstanceName() + 
            " -> Specified driving strength string does not contain any driving strengths (" +
            driving_strengths_ + ")");

        // TODO - check if the driving strengths is sorted

        // Overwrite the available driving strengths
        m_driving_strengths_.clear();
        for(int i = 0; i < (int)split_str.size(); ++i)
        {
            m_driving_strengths_.push_back(split_str[i].toDouble());
        }

        // Set the driving strength to minimum
        m_curr_driving_strengths_idx_ = 0;
        setProperty("DrivingStrength", m_driving_strengths_[m_curr_driving_strengths_idx_]);
        return;
    }
    
    // Connect a port (input or output) to some ElectricalNet
    void ElectricalModel::portConnect(ElectricalModel* connect_model_, const String& connect_port_name_, const String& connect_net_name_)
    {
        ASSERT(m_net_references_->keyExist(connect_net_name_), "[Error] " + getInstanceName() +
            " -> Net '" + connect_net_name_ + "' does not exist!");
            
        portConnect(connect_model_, connect_port_name_, connect_net_name_, m_net_references_->get(connect_net_name_));        
    }
        
    void ElectricalModel::portConnect(ElectricalModel* connect_model_, const String& connect_port_name_, const String& connect_net_name_, const NetIndex& connect_net_indices_)
    {
        ASSERT(m_net_references_->keyExist(connect_net_name_), "[Error] " + getInstanceName() +
            " -> Net '" + connect_net_name_ + "' does not exist!");
            
        // Check whether the port name is an input or output, ASSERTion error if neither
        bool is_input = connect_model_->getInputs()->keyExist(connect_port_name_);
        bool is_output = connect_model_->getOutputs()->keyExist(connect_port_name_);
        
        ASSERT(is_input || is_output, "[Error] " + getInstanceName() + " -> Model '" + connect_model_->getInstanceName() + 
            "' does not have a port named '" + connect_port_name_ + "'!");
        
        int connect_net_width = connect_net_indices_.second - connect_net_indices_.first + 1;
        const NetIndex& port_indices = connect_model_->getNetReference(connect_port_name_);
        int port_width = port_indices.second - port_indices.first + 1;
        
        ASSERT(connect_net_width == port_width, "[Error] " + getInstanceName() + " -> Port width mismatch for Model '" + 
            connect_model_->getInstanceName() + "." + connect_port_name_ + toString(port_indices) + 
            "' and net '" + connect_net_name_ + toString(connect_net_indices_) + "'!");

        int port_index = port_indices.first;
        int connect_net_index = connect_net_indices_.first;
        
        if(is_input)
        {
            while(port_index <= port_indices.second)
            {
                getNet(connect_net_name_, makeNetIndex(connect_net_index))->addDownstreamNode(
                    connect_model_->getNet(connect_port_name_, makeNetIndex(port_index)));
                ++port_index;
                ++connect_net_index;
            }
        }
        else if(is_output)
        {
            while (port_index <= port_indices.second)
            {
                connect_model_->getNet(connect_port_name_, makeNetIndex(port_index))->addDownstreamNode(
                    getNet(connect_net_name_, makeNetIndex(connect_net_index)));
                ++port_index;
                ++connect_net_index;
            }
        }
    }
    
    //Get Drivers
    const Map<ElectricalDriver*>* ElectricalModel::getDrivers() const
    {
        return m_drivers_;
    }
    
    ElectricalDriver* ElectricalModel::getDriver(const String& name_)
    {
        return m_drivers_->get(name_);
    }

    //Get Driver Multipliers
    const Map<ElectricalDriverMultiplier*>* ElectricalModel::getDriverMultipliers() const
    {
        return m_driver_multipliers_;
    }
    
    ElectricalDriverMultiplier* ElectricalModel::getDriverMultiplier(const String& name_)
    {
        return m_driver_multipliers_->get(name_);
    }

    //Get Nets
    const Map<ElectricalNet*>* ElectricalModel::getNets() const
    {
        return m_nets_;
    }
    
    ElectricalNet* ElectricalModel::getNet(const String& name_)
    {
        return getNet(name_, m_net_references_->get(name_));
    }

    ElectricalNet* ElectricalModel::getNet(const String& name_, const NetIndex& index_)
    {
        ASSERT(index_.first == index_.second, "[Error] " + getInstanceName() +
            " -> Ambiguous get net since (" + name_ + ") is a bus consisting of several nets!");
        return m_nets_->get(name_ + "[" + (String) index_.first + "]");
    }
    
    //Get Loads
    const Map<ElectricalLoad*>* ElectricalModel::getLoads() const
    {
        return m_loads_;
    }

    ElectricalLoad* ElectricalModel::getLoad(const String& name_)
    {
        return m_loads_->get(name_);
    }
    
    //Get Delays
    const Map<ElectricalDelay*>* ElectricalModel::getDelays() const
    {
        return m_delays_;
    }

    ElectricalDelay* ElectricalModel::getDelay(const String& name_)
    {
        return m_delays_->get(name_);
    }

    //Get Inputs
    const Map<PortInfo*>* ElectricalModel::getInputs() const
    {
        return m_input_ports_;
    }
    
    PortInfo* ElectricalModel::getInputPort(const String& name_)
    {
        ASSERT(m_input_ports_->keyExist(name_), "[Error] " + getInstanceName() +
                " -> Input port (" + name_ + ") does not exist");

        return m_input_ports_->get(name_);
    }

    const PortInfo* ElectricalModel::getInputPort(const String& name_) const
    {
        ASSERT(m_input_ports_->keyExist(name_), "[Error] " + getInstanceName() +
                " -> Input port (" + name_ + ") does not exist");

        return m_input_ports_->get(name_);
    }

    //Get Outputs
    const Map<PortInfo*>* ElectricalModel::getOutputs() const
    {
        return m_output_ports_;
    }

    PortInfo* ElectricalModel::getOutputPort(const String& name_)
    {
        ASSERT(m_output_ports_->keyExist(name_), "[Error] " + getInstanceName() +
                " -> Output port (" + name_ + ") does not exist");

        return m_output_ports_->get(name_);
    }
    
    const PortInfo* ElectricalModel::getOutputPort(const String& name_) const
    {
        ASSERT(m_output_ports_->keyExist(name_), "[Error] " + getInstanceName() +
                " -> Output port (" + name_ + ") does not exist");

        return m_output_ports_->get(name_);
    }
    
    const Map<NetIndex>* ElectricalModel::getNetReferences() const
    {
        return m_net_references_;
    }

    const NetIndex ElectricalModel::getNetReference(const String& name_) const
    {
        return m_net_references_->get(name_);
    }

    //-------------------------------------------------------------------------
    //  Electrical Connectivity and Timing Element Creation Functions
    //-------------------------------------------------------------------------

    // Input Port creation
    void ElectricalModel::createInputPort(const String& name_, const NetIndex& net_indices_)
    {
        // Create the new nets (including its net reference)
        // This should already check that it has not been previously declared
        createNet(name_, net_indices_);
        // Add the net name to list of input ports
        m_input_ports_->set(name_, new PortInfo(name_, net_indices_));
        return;
    }

    // Output Port creation
    void ElectricalModel::createOutputPort(const String& name_, const NetIndex& net_indices_)
    {
        // Create the new nets (including its net reference)
        // This should already check that it has not been previously declared
        createNet(name_, net_indices_);
        // Add the net name to list of output ports
        m_output_ports_->set(name_, new PortInfo(name_, net_indices_));
        return;
    }

    // Net creation
    void ElectricalModel::createNet(const String& name_)
    {
        // Creating a net with specifying an index range means that the net is just
        // a 1-bit wire indexed at [0]
        createNet(name_, makeNetIndex(0, 0));
        return;
    }

    void ElectricalModel::createNet(const String& name_, const NetIndex& net_indices_)
    {
        // Check that it hasn't been previously declared
        ASSERT( !m_nets_->keyExist(name_) && !m_net_references_->keyExist(name_),
                "[Error] " + getInstanceName() + " -> Redeclaration of net " + name_);

        int start = net_indices_.first;
        int end = net_indices_.second;
        
        for (int index = start; index <= end; ++index)
        {
            String indexed_name = name_ + "[" + (String) index + "]";
            // Create the new net
            ElectricalNet* net = new ElectricalNet(indexed_name, this);
            // Add the net to net map
            m_nets_->set(indexed_name, net);
        }
        // Add net to net references
        m_net_references_->set(name_, net_indices_);        
        return;
    }
    
    // Driver creation
    void ElectricalModel::createDriver(const String& name_, bool sizable_)
    {
        // Check that it hasn't been previously declared
        ASSERT( !m_drivers_->keyExist(name_),
                "[Error] " + getInstanceName() + " -> Redeclaration of driver " + name_);

        ElectricalDriver* driver = new ElectricalDriver(name_, this, sizable_);
        m_drivers_->set(name_, driver);
        return;
    }

    /*
    void ElectricalModel::createDriver(const String& name_, bool sizable_, int start_index_, int end_index_)
    {
        for (int index = start_index_; index <= end_index_; ++index)
        {
            createDriver(name_ + "[" + (String) index + "]", sizable_);
        }
        return;
    }
    */
    
    // Driver Multiplier creation
    void ElectricalModel::createDriverMultiplier(const String& name_)
    {
        // Check that it hasn't been previously declared
        ASSERT( !m_driver_multipliers_->keyExist(name_),
                "[Error] " + getInstanceName() + " -> Redeclaration of driver_multiplier " + name_);

        ElectricalDriverMultiplier* driver_multiplier = new ElectricalDriverMultiplier(name_, this);
        m_driver_multipliers_->set(name_, driver_multiplier);
        return;
    }

    // Load creation
    
    void ElectricalModel::createLoad(const String& name_)
    {
        // Check that it hasn't been previously declared
        ASSERT( !m_loads_->keyExist(name_),
                "[Error] " + getInstanceName() + " -> Redeclaration of load " + name_);

        ElectricalLoad* load = new ElectricalLoad(name_, this);
        m_loads_->set(name_, load);
        return;
    }
    
    /*
    void ElectricalModel::createLoad(const String& name_, int start_index_, int end_index_)
    {
        for (int index = start_index_; index <= end_index_; ++index)
        {
            createLoad(name_ + "[" + (String) index + "]");
        }
        return;
    }
    */

    // Delay creation
    void ElectricalModel::createDelay(const String& name_)
    {
        // Check that it hasn't been previously declared
        ASSERT( !m_delays_->keyExist(name_),
                "[Error] " + getInstanceName() + " -> Redeclaration of delay " + name_);

        ElectricalDelay* delay = new ElectricalDelay(name_, this);
        m_delays_->set(name_, delay);
        return;
    }

    /*
    void ElectricalModel::createDelay(const String& name_, int start_index_, int end_index_)
    {
        for (int index = start_index_; index <= end_index_; ++index)
        {
            createDelay(name_ + "[" + (String) index + "]");
        }
        return;
    }
    */
    //-------------------------------------------------------------------------
    
    // Assign a net to be downstream from another net
    // case 1: 'assign downstream_net_name_ = upstream_net_name_'
    void ElectricalModel::assign(const String& downstream_net_name_, const String& upstream_net_name_)
    {
        ASSERT(getNetReferences()->keyExist(downstream_net_name_), "[Error] " + getInstanceName() + " -> Net '" +
            downstream_net_name_ + "' does not exist!");

        ASSERT(getNetReferences()->keyExist(upstream_net_name_), "[Error] " + getInstanceName() + " -> Net '" +
            upstream_net_name_ + "' does not exist!");
        
        assign(downstream_net_name_, getNetReference(downstream_net_name_),
            upstream_net_name_, getNetReference(upstream_net_name_));
            
        return;
    }

    // case 2: 'assign downstream_net_name_[begin:end] = upstream_net_name_'
    void ElectricalModel::assign(const String& downstream_net_name_, const NetIndex& downstream_net_indices_, const String& upstream_net_name_)
    {
        ASSERT(getNetReferences()->keyExist(downstream_net_name_), "[Error] " + getInstanceName() + " -> Net '" +
            downstream_net_name_ + "' does not exist!");

        ASSERT(getNetReferences()->keyExist(upstream_net_name_), "[Error] " + getInstanceName() + " -> Net '" +
            upstream_net_name_ + "' does not exist!");
            
        assign(downstream_net_name_, downstream_net_indices_,
            upstream_net_name_, getNetReference(upstream_net_name_));

        return;
    }

    // case 3: 'assign downstream_net_name_ = upstream_net_name_[begin:end]'
    void ElectricalModel::assign(const String& downstream_net_name_, const String& upstream_net_name_, const NetIndex& upstream_net_indices_)
    {
        ASSERT(getNetReferences()->keyExist(downstream_net_name_), "[Error] " + getInstanceName() + " -> Net '" +
            downstream_net_name_ + "' does not exist!");

        ASSERT(getNetReferences()->keyExist(upstream_net_name_), "[Error] " + getInstanceName() + " -> Net '" +
            upstream_net_name_ + "' does not exist!");
            
        assign(downstream_net_name_, getNetReference(downstream_net_name_),
            upstream_net_name_, upstream_net_indices_);

        return;    
    }
    // case 4: 'assign downstream_net_name_[begin:end] = upstream_net_name_[begin:end]'
    void ElectricalModel::assign(const String& downstream_net_name_, const NetIndex& downstream_net_indices_, const String& upstream_net_name_, const NetIndex& upstream_net_indices_)
    {
        ASSERT(getNetReferences()->keyExist(downstream_net_name_), "[Error] " + getInstanceName() + " -> Net '" +
            downstream_net_name_ + "' does not exist!");

        ASSERT(getNetReferences()->keyExist(upstream_net_name_), "[Error] " + getInstanceName() + " -> Net '" +
            upstream_net_name_ + "' does not exist!");
            
        // Check that the assignment widths are the same
        int downstream_width = downstream_net_indices_.second - downstream_net_indices_.first + 1;
        int upstream_width = upstream_net_indices_.second - upstream_net_indices_.first + 1;

        ASSERT(downstream_width == upstream_width, "[Error] " + getInstanceName() + " -> Assignment width mismatch: " +
            downstream_net_name_ + " (" + (String) downstream_width + ") and " +
            upstream_net_name_ + " (" + (String) upstream_width + ")");

        // Loop through indices and connect them together
        int down_index = downstream_net_indices_.first;
        int up_index = upstream_net_indices_.first;        
        while (down_index <= downstream_net_indices_.second)
        {
            getNet(upstream_net_name_, makeNetIndex(up_index))->addDownstreamNode(
                getNet(downstream_net_name_, makeNetIndex(down_index)));
            
            ++up_index;
            ++down_index;
        }
        
        return;
    }

    // Assign a net to another net through a driver multiplier
    void ElectricalModel::assignVirtualFanout(const String& downstream_net_name_, const String& upstream_net_name_)
    {
        ASSERT(getNetReferences()->keyExist(upstream_net_name_), "[Error] " + getInstanceName() + 
                " -> Net '" + upstream_net_name_ + "' does not exist!");
        ASSERT(getNetReferences()->keyExist(downstream_net_name_), "[Error] " + getInstanceName() + 
                " -> Net '" + downstream_net_name_ + "' does not exist!");

        assignVirtualFanout(downstream_net_name_, getNetReference(downstream_net_name_), upstream_net_name_, getNetReference(upstream_net_name_));
        return;
    }

    // Assign a net to another net through a driver multiplier
    void ElectricalModel::assignVirtualFanout(const String& downstream_net_name_, const NetIndex& downstream_net_indices_, const String& upstream_net_name_, const NetIndex& upstream_net_indices_)
    {
        ASSERT(getNetReferences()->keyExist(upstream_net_name_), "[Error] " + getInstanceName() + 
                " -> Net '" + upstream_net_name_ + "' does not exist!");
        ASSERT(getNetReferences()->keyExist(downstream_net_name_), "[Error] " + getInstanceName() + 
                " -> Net '" + downstream_net_name_ + "' does not exist!");

        const String& drive_mult_name = upstream_net_name_ + "_" + (String) upstream_net_indices_.first + "_DriverMultiplier";
        bool is_drive_mult_exist = getDriverMultipliers()->keyExist(drive_mult_name);

        // Create a driver multiplier and assign it to upstream_net since it doesn't exist 
        if(!is_drive_mult_exist)
        {
            createDriverMultiplier(drive_mult_name);
            getNet(upstream_net_name_, upstream_net_indices_)->addDownstreamNode(getDriverMultiplier(drive_mult_name));
        }

        // Assign downstream_net_name_[end:begin] = driver_multiplier_name_
        ElectricalDriverMultiplier* drive_mult = getDriverMultiplier(drive_mult_name);
        int begin_index = downstream_net_indices_.first;
        int end_index = downstream_net_indices_.second;
        for(int i = begin_index; i <= end_index; ++i)
        {
            drive_mult->addDownstreamNode(getNet(downstream_net_name_, makeNetIndex(i)));
        }
        return;
    }

    void ElectricalModel::assignVirtualFanin(const String& downstream_net_name_, const String& upstream_net_name_)
    {
        ASSERT(getNetReferences()->keyExist(upstream_net_name_), "[Error] " + getInstanceName() + 
                " -> Net '" + upstream_net_name_ + "' does not exist!");
        ASSERT(getNetReferences()->keyExist(downstream_net_name_), "[Error] " + getInstanceName() + 
                " -> Net '" + downstream_net_name_ + "' does not exist!");

        assignVirtualFanin(downstream_net_name_, getNetReference(downstream_net_name_), upstream_net_name_, getNetReference(upstream_net_name_));
        return;
    }

    void ElectricalModel::assignVirtualFanin(const String& downstream_net_name_, const NetIndex& downstream_net_indices_, const String& upstream_net_name_, const NetIndex& upstream_net_indices_)
    {
        ASSERT(getNetReferences()->keyExist(upstream_net_name_), "[Error] " + getInstanceName() + 
                " -> Net '" + upstream_net_name_ + "' does not exist!");
        ASSERT(getNetReferences()->keyExist(downstream_net_name_), "[Error] " + getInstanceName() + 
                " -> Net '" + downstream_net_name_ + "' does not exist!");

        int begin_index = upstream_net_indices_.first;
        int end_index = upstream_net_indices_.second;

        for(int i = begin_index; i <= end_index; ++i)
        {
            getNet(upstream_net_name_, makeNetIndex(i))->addDownstreamNode(getNet(downstream_net_name_, downstream_net_indices_));
        }
        return;
    }

    void ElectricalModel::createElectricalResults()
    {
        // Add active area result
        addAreaResult(new Result("Active"));

        // Add wire area result
        TechModel::ConstWireLayerIterator it_begin = getTechModel()->getAvailableWireLayers()->begin();
        TechModel::ConstWireLayerIterator it_end = getTechModel()->getAvailableWireLayers()->end();
        TechModel::ConstWireLayerIterator it;
        for(it = it_begin; it != it_end; ++it)
        {
            const String& layer_name = (*it);
            addAreaResult(new Result(layer_name + "Wire"));
        }

        // Add leakage result
        addNddPowerResult(new Result("Leakage"));

        // Add idle event result
        createElectricalEventResult("Idle");
        return;
    }

    void ElectricalModel::addElectricalSubResults(const ElectricalModel* model_, double number_models_)
    {
        // Add active area sub result
        getAreaResult("Active")->addSubResult(model_->getAreaResult("Active"), model_->getInstanceName(), number_models_);

        // Add wire area sub result
        TechModel::ConstWireLayerIterator it_begin = getTechModel()->getAvailableWireLayers()->begin();
        TechModel::ConstWireLayerIterator it_end = getTechModel()->getAvailableWireLayers()->end();
        TechModel::ConstWireLayerIterator it;
        for(it = it_begin; it != it_end; ++it)
        {
            const String& layer_name = (*it);
            const String& result_name = layer_name + "Wire";
            getAreaResult(result_name)->addSubResult(model_->getAreaResult(result_name), model_->getInstanceName(), number_models_);
        }

        // Add leakage sub result
        getNddPowerResult("Leakage")->addSubResult(model_->getNddPowerResult("Leakage"), model_->getInstanceName(), number_models_);

        // Add idle event sub result
        getEventResult("Idle")->addSubResult(model_->getEventResult("Idle"), model_->getInstanceName(), number_models_);
        return;
    }

    void ElectricalModel::addElectricalWireSubResult(const String& wire_layer_, const Result* result_, const String& producer_, double number_results_)
    {
        getAreaResult(wire_layer_ + "Wire")->addSubResult(result_, producer_, number_results_);
        return;
    }

    void ElectricalModel::createElectricalAtomicResults()
    {
        // Add active area result
        addAreaResult(new AtomicResult("Active"));

        // Add wire area result
        TechModel::ConstWireLayerIterator it_begin = getTechModel()->getAvailableWireLayers()->begin();
        TechModel::ConstWireLayerIterator it_end = getTechModel()->getAvailableWireLayers()->end();
        TechModel::ConstWireLayerIterator it;
        for(it = it_begin; it != it_end; ++it)
        {
            const String& layer_name = (*it);
            addAreaResult(new AtomicResult(layer_name + "Wire"));
        }

        // Add leakage result
        addNddPowerResult(new AtomicResult("Leakage"));

        // Add idle event result
        createElectricalEventAtomicResult("Idle");
        return;
    }

    void ElectricalModel::addElecticalAtomicResultValues(const ElectricalModel* model_, double number_models_)
    {
        getAreaResult("Active")->addValue(model_->getAreaResult("Active")->calculateSum() * number_models_);

        // Add wire area sub result
        TechModel::ConstWireLayerIterator it_begin = getTechModel()->getAvailableWireLayers()->begin();
        TechModel::ConstWireLayerIterator it_end = getTechModel()->getAvailableWireLayers()->end();
        TechModel::ConstWireLayerIterator it;
        for(it = it_begin; it != it_end; ++it)
        {
            const String& layer_name = (*it);
            const String& result_name = layer_name + "Wire";
            getAreaResult(result_name)->addValue(model_->getAreaResult(result_name)->calculateSum() * number_models_);
        }

        // Add leakage sub result
        getNddPowerResult("Leakage")->addValue(model_->getNddPowerResult("Leakage")->calculateSum() * number_models_);

        // Add idle event sub result
        getEventResult("Idle")->addValue(model_->getEventResult("Idle")->calculateSum() * number_models_);
        return;
    }

    void ElectricalModel::addElecticalWireAtomicResultValue(const String& wire_layer_, double value_)
    {
        getAreaResult(wire_layer_ + "Wire")->addValue(value_);
        return;
    }

    void ElectricalModel::resetElectricalAtomicResults()
    {
        getAreaResult("Active")->setValue(0.0);

        // Reset wire area sub result
        TechModel::ConstWireLayerIterator it_begin = getTechModel()->getAvailableWireLayers()->begin();
        TechModel::ConstWireLayerIterator it_end = getTechModel()->getAvailableWireLayers()->end();
        TechModel::ConstWireLayerIterator it;
        for(it = it_begin; it != it_end; ++it)
        {
            const String& layer_name = (*it);
            const String& result_name = layer_name + "Wire";
            getAreaResult(result_name)->setValue(0.0);
        }

        // Reset leakage sub result
        getNddPowerResult("Leakage")->setValue(0.0);

        // Reset idle event sub result
        getEventResult("Idle")->setValue(0.0);

        return;
    }

    void ElectricalModel::createElectricalEventResult(const String& name_)
    {
        // Add the event result
        addEventResult(new Result(name_));
        // Add event info
        m_event_infos_->set(name_, new EventInfo(name_, getInputs()));
        return;
    }

    void ElectricalModel::createElectricalEventAtomicResult(const String& name_)
    {
        // Add the event result
        addEventResult(new AtomicResult(name_));
        // Add event info
        m_event_infos_->set(name_, new EventInfo(name_, getInputs()));
        return;
    }

    void ElectricalModel::assignPortTransitionInfo(ElectricalModel* downstream_model_, const String& downstream_port_name_, const TransitionInfo& trans_info_)
    {
        ASSERT(downstream_model_ != NULL, "[Error] " + getInstanceName() +
                " -> Downstream model does not exist");

        downstream_model_->getInputPort(downstream_port_name_)->setTransitionInfo(trans_info_);
        return;
    }

    void ElectricalModel::propagatePortTransitionInfo(const String& downstream_port_name_, const String& upstream_port_name_)
    {
        const TransitionInfo& trans_info = getInputPort(upstream_port_name_)->getTransitionInfo();
        getOutputPort(downstream_port_name_)->setTransitionInfo(trans_info);
        return;
    }

    void ElectricalModel::propagatePortTransitionInfo(ElectricalModel* downstream_model_, const String& downstream_port_name_, const String& upstream_port_name_)
    {
        ASSERT(downstream_model_ != NULL, "[Error] " + getInstanceName() +
                " -> Downstream model does not exist");

        const TransitionInfo& trans_info = getInputPort(upstream_port_name_)->getTransitionInfo();
        downstream_model_->getInputPort(downstream_port_name_)->setTransitionInfo(trans_info);
        return;
    }

    void ElectricalModel::propagatePortTransitionInfo(ElectricalModel* downstream_model_, const String& downstream_port_name_, const ElectricalModel* upstream_model_, const String& upstream_port_name_)
    {
        ASSERT(downstream_model_ != NULL, "[Error] " + getInstanceName() +
                " -> Downstream model does not exist");
        ASSERT(upstream_model_ != NULL, "[Error] " + getInstanceName() +
                " -> Upstream model does not exist");

        const TransitionInfo& trans_info = upstream_model_->getOutputPort(upstream_port_name_)->getTransitionInfo();

        downstream_model_->getInputPort(downstream_port_name_)->setTransitionInfo(trans_info);
        return;
    }

    void ElectricalModel::propagatePortTransitionInfo(const String& downstream_port_name_, const ElectricalModel* upstream_model_, const String& upstream_port_name_)
    {
        ASSERT(upstream_model_ != NULL, "[Error] " + getInstanceName() +
                " -> Upstream model does not exist");
    
        const TransitionInfo& trans_info = upstream_model_->getOutputPort(upstream_port_name_)->getTransitionInfo();
        getOutputPort(downstream_port_name_)->setTransitionInfo(trans_info);
        return;
    }

    void ElectricalModel::propagateTransitionInfo()
    {
        // by default do nothing.
    }

    void ElectricalModel::useModel(const String& event_name_)
    {
        getGenProperties()->set("UseModelEvent", event_name_);
        applyTransitionInfo(event_name_);
        useModel();
        return;
    }

    void ElectricalModel::useModel()
    {
        propagateTransitionInfo();
        return;
    }

    void ElectricalModel::applyTransitionInfo(const String& event_name_)
    {
        // Check if the event actually exists
        ASSERT(hasEventResult(event_name_), "[Error] " + getInstanceName() +
                " -> Event (" + event_name_ + ") does not exist in the result map");
        ASSERT(m_event_infos_->keyExist(event_name_), "[Error] " + getInstanceName() +
                " -> Event (" + event_name_ + ") does not exist in the event info map");

        const EventInfo* event_info = m_event_infos_->get(event_name_);

        // Set the input ports' transition information for the event
        Map<PortInfo*>::ConstIterator it_begin = m_input_ports_->begin();
        Map<PortInfo*>::ConstIterator it_end = m_input_ports_->end();
        Map<PortInfo*>::ConstIterator it;
        for(it = it_begin; it != it_end; ++it)
        {
            const String& port_name = it->first;
            PortInfo* port_info = it->second;
            const TransitionInfo& trans_info = event_info->getTransitionInfo(port_name);
            port_info->setTransitionInfo(trans_info);
        }

        return;
    }

    EventInfo* ElectricalModel::getEventInfo(const String& event_name_)
    {
        ASSERT(m_event_infos_->keyExist(event_name_), "[Error] " + getInstanceName() +
                " -> Event (" + event_name_ + ") does not exist");

        return m_event_infos_->get(event_name_);
    }

} // namespace DSENT