/*
 * This program source code file is part of KiCad, a free EDA CAD application.
 *
 * Copyright (C) 2016 Jean-Pierre Charras, jp.charras at wanadoo.fr
 * Copyright (C) 2013 Wayne Stambaugh <stambaughw@verizon.net>
 * Copyright (C) 1992-2016 KiCad Developers, see AUTHORS.txt for contributors.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program 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, you may find one here:
 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
 * or you may search the http://www.gnu.org website for the version 2 license,
 * or you may write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 */

/**
 * @file netlist_object.cpp
 * @brief Class NETLIST_OBJECT to handle 1 item connected (in netlist and erc calculations)
 */

#include <fctsys.h>
#include <macros.h>
#include <list>

#include <sch_component.h>
#include <sch_connection.h>
#include <netlist_object.h>
#include <sch_edit_frame.h>

#if defined(DEBUG)

#include <iostream>
const char* ShowType( NETLIST_ITEM aType )
{
    const char* ret;

    switch( aType )
    {
    case NETLIST_ITEM::SEGMENT:
        ret = "segment";            break;

    case NETLIST_ITEM::BUS:
        ret = "bus";                break;

    case NETLIST_ITEM::JUNCTION:
        ret = "junction";           break;

    case NETLIST_ITEM::LABEL:
        ret = "label";              break;

    case NETLIST_ITEM::HIERLABEL:
        ret = "hierlabel";          break;

    case NETLIST_ITEM::GLOBLABEL:
        ret = "glabel";             break;

    case NETLIST_ITEM::BUSLABELMEMBER:
        ret = "buslblmember";       break;

    case NETLIST_ITEM::HIERBUSLABELMEMBER:
        ret = "hierbuslblmember";   break;

    case NETLIST_ITEM::GLOBBUSLABELMEMBER:
        ret = "gbuslblmember";      break;

    case NETLIST_ITEM::SHEETBUSLABELMEMBER:
        ret = "sbuslblmember";      break;

    case NETLIST_ITEM::SHEETLABEL:
        ret = "sheetlabel";         break;

    case NETLIST_ITEM::PINLABEL:
        ret = "pinlabel";           break;

    case NETLIST_ITEM::PIN:
        ret = "pin";                break;

    case NETLIST_ITEM::NOCONNECT:
        ret = "noconnect";          break;

    default:
        ret = "??";                 break;
    }

    return ret;
}


void NETLIST_OBJECT::Show( std::ostream& out, int ndx ) const
{
    wxString path = m_SheetPath.PathHumanReadable();

    out << "<netItem ndx=\"" << ndx << '"' <<
    " type=\"" << ShowType( m_Type ) << '"' <<
    " netCode=\"" << GetNet() << '"' <<
    " sheet=\"" << TO_UTF8( path ) << '"' <<
    ">\n";

    out << " <start " << m_Start << "/> <end " << m_End << "/>\n";

    if( !m_Label.IsEmpty() )
        out << " <label>" << m_Label.mb_str() << "</label>\n";

    out << " <sheetpath>" << m_SheetPath.PathHumanReadable().mb_str() << "</sheetpath>\n";

    switch( m_Type )
    {
    case NETLIST_ITEM::PIN:
        /* GetRef() needs to be const
        out << " <refOfComp>" << GetComponentParent()->GetRef(&m_SheetPath).mb_str()
            << "</refOfComp>\n";
        */

        if( m_Comp )
            m_Comp->Show( 1, out );

        break;

    default:
        // not all the m_Comp classes have working Show functions.
        ;
    }

/*  was segfault-ing
    if( m_Comp )
        m_Comp->Show( 1, out );     // labels may not have good Show() funcs?
    else
        out << " m_Comp==NULL\n";
*/

    out << "</netItem>\n";
}

#endif


NETLIST_OBJECT::NETLIST_OBJECT()
{
    m_Type = NETLIST_ITEM::ITEM_UNSPECIFIED; /* Type of this item (see NETLIST_ITEM_T enum) */
    m_Comp = NULL;    /* Pointer on the library item that created this net object
                       * (the parent)*/
    m_Link = NULL;    /* For SCH_SHEET_PIN:
                       * Pointer to the hierarchy sheet that contains this
                       * SCH_SHEET_PIN For Pins: pointer to the component that
                       * contains this pin
                       */
    m_Flag    = 0;    /* flag used in calculations */
    m_netCode = 0;    /* net code for all items except BUS labels because a BUS
                       * label has as many net codes as bus members
                       */
    m_BusNetCode = 0; /* Used for BUS connections */
    m_Member     = 0; /* for labels type NETLIST_ITEM::BUSLABELMEMBER ( bus member created
                       * from the BUS label )  member number
                       */
    m_ConnectionType = NET_CONNECTION::UNCONNECTED;
    m_ElectricalPinType =
            ELECTRICAL_PINTYPE::PT_INPUT; /* Has meaning only for Pins: electrical type of the pin
                                        * used to detect conflicts between pins in ERC
                                        */
    m_netNameCandidate = NULL;         /* a pointer to a NETLIST_OBJECT type label connected to this
                                        * object used to give a name to the net
                                        */
}


// Copy constructor
NETLIST_OBJECT::NETLIST_OBJECT( NETLIST_OBJECT& aSource )
{
    *this = aSource;
}


NETLIST_OBJECT::~NETLIST_OBJECT()
{
}


// return true if the object is a label of any type
bool NETLIST_OBJECT::IsLabelType() const
{
    return m_Type == NETLIST_ITEM::LABEL || m_Type == NETLIST_ITEM::GLOBLABEL
           || m_Type == NETLIST_ITEM::HIERLABEL || m_Type == NETLIST_ITEM::BUSLABELMEMBER
           || m_Type == NETLIST_ITEM::GLOBBUSLABELMEMBER
           || m_Type == NETLIST_ITEM::HIERBUSLABELMEMBER || m_Type == NETLIST_ITEM::PINLABEL;
}

bool NETLIST_OBJECT::IsLabelConnected( NETLIST_OBJECT* aNetItem )
{
    if( aNetItem == this )   // Don't compare the same net list object.
        return false;

    NETLIST_ITEM at = m_Type;
    NETLIST_ITEM bt = aNetItem->m_Type;

    if( ( at == NETLIST_ITEM::HIERLABEL || at == NETLIST_ITEM::HIERBUSLABELMEMBER )
            && ( bt == NETLIST_ITEM::SHEETLABEL || bt == NETLIST_ITEM::SHEETBUSLABELMEMBER ) )
    {
        if( m_SheetPath == aNetItem->m_SheetPathInclude )
        {
            return true; //connected!
        }
    }
    else if( ( at == NETLIST_ITEM::GLOBLABEL ) && ( bt == NETLIST_ITEM::GLOBLABEL ) )
    {
        if( m_Label == aNetItem->m_Label )
            return true; //connected!
    }

    return false; //these two are unconnected
}


void NETLIST_OBJECT::ConvertBusToNetListItems( NETLIST_OBJECT_LIST& aNetListItems )
{
    SCH_CONNECTION conn;
    wxCHECK_RET( conn.IsBusLabel( m_Label ),
                 wxT( "<" ) + m_Label + wxT( "> is not a valid bus label." ) );

    if( m_Type == NETLIST_ITEM::HIERLABEL )
        m_Type = NETLIST_ITEM::HIERBUSLABELMEMBER;
    else if( m_Type == NETLIST_ITEM::GLOBLABEL )
        m_Type = NETLIST_ITEM::GLOBBUSLABELMEMBER;
    else if( m_Type == NETLIST_ITEM::SHEETLABEL )
        m_Type = NETLIST_ITEM::SHEETBUSLABELMEMBER;
    else if( m_Type == NETLIST_ITEM::LABEL )
        m_Type = NETLIST_ITEM::BUSLABELMEMBER;
    else
        wxCHECK_RET( false, wxT( "Net list object type is not valid." ) );

    // NOTE: all netlist objects generated from a single bus definition need to have different
    // member codes set.  For bus vectors, the member code matches the vector index, but for
    // bus groups (including with nested vectors) the code is something arbitrary.
    long member_offset = 0;

    auto alias = SCH_SCREEN::GetBusAlias( m_Label );
    if( alias || conn.IsBusGroupLabel( m_Label ) )
    {
        wxString group_name;
        bool self_set = false;
        std::vector<wxString> bus_contents_vec;

        if( alias )
        {
            for( const auto& member : alias->Members() )
                bus_contents_vec.emplace_back( member );
        }
        else
        {
            wxCHECK_RET( conn.ParseBusGroup( m_Label, &group_name, bus_contents_vec ),
                         wxString::Format( _( "Failed to parse bus group %s" ), m_Label ) );
        }

        // For named bus groups, like "USB{DP DM}"
        auto group_prefix = ( group_name != "" ) ? ( group_name + "." ) : "";

        std::list<wxString> bus_contents( bus_contents_vec.begin(),
                                          bus_contents_vec.end() );

        for( const auto& bus_member : bus_contents )
        {
            // Nested bus vector inside a bus group
            if( conn.IsBusVectorLabel( bus_member ) )
            {
                wxString prefix;
                std::vector<wxString> members;
                long begin, end;

                conn.ParseBusVector( bus_member, &prefix, members );
                prefix = group_prefix + prefix;
                begin = conn.VectorStart();
                end = conn.VectorEnd();

                if( !self_set )
                {
                    m_Label = members[0];
                    m_Member = ( begin++ ) + ( member_offset++ );

                    self_set = true;
                    begin++;
                }

                fillBusVector( aNetListItems, prefix, begin, end, member_offset );
                member_offset += std::abs( end - begin );
            }
            else if( auto nested_alias = SCH_SCREEN::GetBusAlias( bus_member ) )
            {
                // Nested alias inside a group
                for( const auto& alias_member : nested_alias->Members() )
                {
                    bus_contents.push_back( alias_member );
                }
            }
            else
            {
                if( !self_set )
                {
                    m_Label = group_prefix + bus_member;
                    m_Member = member_offset++;
                    self_set = true;
                }
                else
                {
                    auto item = new NETLIST_OBJECT( *this );
                    item->m_Label = group_prefix + bus_member;
                    item->m_Member = member_offset++;
                    aNetListItems.push_back( item );
                }
            }
        }
    }
    else
    {
        // Plain bus vector
        wxString prefix;
        std::vector<wxString> members;
        long begin, end;

        conn.ParseBusVector( m_Label, &prefix, members );
        begin = conn.VectorStart();
        end = conn.VectorEnd();

        m_Label = members[0];
        m_Member = begin;

        fillBusVector( aNetListItems, prefix, begin + 1, end, 0 );
    }
}

void NETLIST_OBJECT::fillBusVector( NETLIST_OBJECT_LIST& aNetListItems,
                                    wxString aName, long aBegin, long aEnd, long aOffset )
{
    for( long member = aBegin; member <= aEnd; member++ )
    {
        auto item = new NETLIST_OBJECT( *this );

        item->m_Label = aName;
        item->m_Label << member;
        item->m_Member = member;

        aNetListItems.push_back( item );
    }
}


bool NETLIST_OBJECT::IsLabelGlobal() const
{
    // return true if the object is a global label
    // * a actual global label
    // * a pin label coming from a invisible power pin
    return ( m_Type == NETLIST_ITEM::PINLABEL ) || ( m_Type == NETLIST_ITEM::GLOBLABEL )
           || ( m_Type == NETLIST_ITEM::GLOBBUSLABELMEMBER );
}


bool NETLIST_OBJECT::IsLabelBusMemberType() const
{
    // return true if the object is a bus label member build from a
    // schematic bus label (like label[xx..yy)
    // They are labels with very specific properties, especially for connection
    // between them: 2 bus label members can be connected only
    // if they have the same member value.
    return ( m_Type == NETLIST_ITEM::SHEETBUSLABELMEMBER )
           || ( m_Type == NETLIST_ITEM::BUSLABELMEMBER )
           || ( m_Type == NETLIST_ITEM::HIERBUSLABELMEMBER )
           || ( m_Type == NETLIST_ITEM::GLOBBUSLABELMEMBER );
}


/*
 * return the net name of the item
 */
wxString NETLIST_OBJECT::GetNetName() const
{
    if( m_netNameCandidate == NULL )
        return wxEmptyString;

    wxString netName;

    if( m_netNameCandidate->m_Type == NETLIST_ITEM::PIN )
        return GetShortNetName();

    if( !m_netNameCandidate->IsLabelGlobal() )
    {
        // usual net name, prefix it by the sheet path
        netName = m_netNameCandidate->m_SheetPath.PathHumanReadable();
    }

    netName += m_netNameCandidate->m_Label;

    return netName;
}

/**
 * return the short net name of the item i.e. the net name
 * from the "best" label without any prefix.
 * 2 different nets can have the same short name
 */
wxString NETLIST_OBJECT::GetShortNetName() const
{
    if( m_netNameCandidate == NULL )
        return wxEmptyString;

    wxString netName;

    if( m_netNameCandidate->m_Type == NETLIST_ITEM::PIN )
    {
        SCH_COMPONENT* link = m_netNameCandidate->GetComponentParent();
        if( link )  // Should be always true
        {
            netName = wxT("Net-(");
            netName << link->GetRef( &m_netNameCandidate->m_SheetPath );
            netName << wxT("-Pad") << m_netNameCandidate->m_PinNum << wxT(")");
        }
    }
    else
        netName = m_netNameCandidate->m_Label;

    return netName;
}

/**
 * Set m_netNameCandidate to a connected item which will
 * be used to calcule the net name of the item
 * Obviously the candidate can be only a label
 * If there is no label on the net, a pad name will be
 * used to build a net name (something like Cmp<REF>_Pad<PAD_NAME>
 * @param aCandidate = the connected item candidate
 */
void NETLIST_OBJECT::SetNetNameCandidate( NETLIST_OBJECT* aCandidate )
{
    switch( aCandidate->m_Type )
    {
    case NETLIST_ITEM::HIERLABEL:
    case NETLIST_ITEM::LABEL:
    case NETLIST_ITEM::PINLABEL:
    case NETLIST_ITEM::GLOBLABEL:
    case NETLIST_ITEM::GLOBBUSLABELMEMBER:
    case NETLIST_ITEM::SHEETBUSLABELMEMBER:
    case NETLIST_ITEM::PIN:
        m_netNameCandidate = aCandidate;
        break;

    default:
        break;
    }
}


const wxString NETLIST_OBJECT::GetPinNameText() const
{
    wxString name;
    // returns the pin name, for NETLIST_ITEM::PIN (usual pin) item.
    if( m_Type == NETLIST_ITEM::PIN )
    {
        name = static_cast<LIB_PIN*>( m_Comp )->GetName();

        if( name == "~" )   //empty name
            name = wxEmptyString;
    }

    return name;
}
