/*****************************/
/* Net list generation code. */
/*****************************/

#include "fctsys.h"

#include <wx/xml/xml.h>

#include "gr_basic.h"
#include "common.h"
#include "confirm.h"
#include "kicad_string.h"
#include "gestfich.h"
#include "appl_wxstruct.h"

#include "program.h"
#include "general.h"
#include "netlist.h"
#include "protos.h"
#include "class_library.h"
#include "class_pin.h"

#include "build_version.h"

/**
 * @bug - Every place in this file where fprintf() is used and the return
 *        is not checked is a bug.  The fprintf() function can fail and
 *        returns a value less than 0 when it does.
 */

static bool Write_GENERIC_NetList( WinEDA_SchematicFrame* frame, const wxString& aOutFileName );

static bool WriteNetListPCBNEW( WinEDA_SchematicFrame* frame, FILE* f,
                                bool with_pcbnew );

static void WriteNetListCADSTAR( WinEDA_SchematicFrame* frame, FILE* f );
static void WriteListOfNetsCADSTAR( FILE* f, NETLIST_OBJECT_LIST& aObjectsList );

static void WriteNetListPspice( WinEDA_SchematicFrame* frame, FILE* f,
                                bool use_netnames );

static bool WriteGENERICListOfNets( FILE* f, NETLIST_OBJECT_LIST& aObjectsList );

static void AddPinToComponentPinList( SCH_COMPONENT*  Component,
                                      SCH_SHEET_PATH* sheet,
                                      LIB_PIN*        PinEntry );

static void FindAllsInstancesOfComponent( SCH_COMPONENT*  Component,
                                          LIB_COMPONENT*  aEntry,
                                          SCH_SHEET_PATH* Sheet_in );

static bool SortPinsByNum( NETLIST_OBJECT* Pin1, NETLIST_OBJECT* Pin2 );
static void EraseDuplicatePins( NETLIST_OBJECT_LIST& aPinList );


static NETLIST_OBJECT_LIST s_SortedComponentPinList;


#include <set>

/**
 * Class UNIQUE_STRINGS
 * will keep track of unique wxStrings and is useful in telling if a string
 * has been seen before.
 */
class UNIQUE_STRINGS
{
private:
    std::set<wxString>      m_set;    ///< set of wxStrings already found

public:
    /**
     * Function Clear
     * erases the record.
     */
    void Clear()  {  m_set.clear();  }

    /**
     * Function Lookup
     * returns true if \a aString already exists in the set, otherwise returns
     * false and adds \a aString to the set for next time.
     */
    bool Lookup( const wxString& aString );
};

bool UNIQUE_STRINGS::Lookup( const wxString& aString )
{
    bool ret = ( m_set.find( aString ) != m_set.end() );
    if( !ret )
        m_set.insert( aString );

    return ret;
}

/// Used for multi part per package components, avoids using a component more than once.
static UNIQUE_STRINGS s_ReferencesAlreadyFound;


/**
 * Function  WriteNetListFile
 * creates the netlist file. Netlist info must be existing
 * @param aFormat = netlist format (NET_TYPE_PCBNEW ...)
 * @param aFullFileName = full netlist file name
 * @param aUse_netnames = bool. if true, use net names from labels in schematic
 *                              if false, use net numbers (net codes)
 *   bool aUse_netnames is used only for Spice netlist
 * @return true if success.
 */
bool WinEDA_SchematicFrame::WriteNetListFile( int aFormat, const wxString& aFullFileName,
                   bool aUse_netnames )
{
    bool    ret = true;
    FILE*   f = NULL;

    if( aFormat < NET_TYPE_CUSTOM1 )
    {
        if( ( f = wxFopen( aFullFileName, wxT( "wt" ) ) ) == NULL )
        {
            wxString msg = _( "Failed to create file " ) + aFullFileName;
            DisplayError( this, msg );
            return false;
        }
    }

    wxBusyCursor Busy;

    switch( aFormat )
    {
    case NET_TYPE_PCBNEW:
        ret = WriteNetListPCBNEW( this, f, TRUE );
        fclose( f );
        break;

    case NET_TYPE_ORCADPCB2:
        ret = WriteNetListPCBNEW( this, f, FALSE );
        fclose( f );
        break;

    case NET_TYPE_CADSTAR:
        WriteNetListCADSTAR( this, f );
        fclose( f );
        break;

    case NET_TYPE_SPICE:
        WriteNetListPspice( this, f, aUse_netnames );
        fclose( f );
        break;

    default:
        {
            wxFileName  tmpFile = aFullFileName;
            tmpFile.SetExt( wxT( "tmp" ) );

            ret = Write_GENERIC_NetList( this, tmpFile.GetFullPath() );
            if( !ret )
                break;

            // Call the external module (plug in )
            if( g_NetListerCommandLine.IsEmpty() )
                break;

            wxString commandLine;

            if( wxIsAbsolutePath( g_NetListerCommandLine ) )
                commandLine = g_NetListerCommandLine;
            else
                commandLine = FindKicadFile( g_NetListerCommandLine );

            // this is the input file to the plugin
            commandLine += wxT( " " ) + tmpFile.GetFullPath();

            // this is the output file to the plugin
            commandLine += wxT( " " ) + aFullFileName;

            ProcessExecute( commandLine, wxEXEC_SYNC );

            // ::wxRemoveFile( tmpFile.GetFullPath() );
        }
        break;
    }

    return ret;
}


/*  Find a "suitable" component from the DrawList
 *  build its pin list s_SortedComponentPinList.
 *  The list is sorted by pin num
 *  A suitable component is a "new" real component (power symbols are not
 *  considered)
 *  Must be deallocated by the user
 */
static SCH_COMPONENT* FindNextComponentAndCreatPinList( EDA_BaseStruct* item,
                                                        SCH_SHEET_PATH* path )
{
    s_SortedComponentPinList.clear();

    // continue searching from the middle of a linked list
    for(  ; item;  item = item->Next() )
    {
        if( item->Type() != TYPE_SCH_COMPONENT )
            continue;

        // found next component
        SCH_COMPONENT* comp = (SCH_COMPONENT*) item;

        // Power symbols and other components which have the reference starting
        // with "#" are not included in netlist (pseudo or virtual components)
        wxString ref = comp->GetRef( path );
        if( ref[0] == wxChar( '#' ) )  // ignore it
            continue;

        // if( Component->m_FlagControlMulti == 1 )
        //    continue;                                      /* yes */
        // removed because with multiple instances of one schematic
        // (several sheets pointing to 1 screen), this will be erroneously be
        // toggled.

        LIB_COMPONENT* entry = CMP_LIBRARY::FindLibraryComponent( comp->m_ChipName );
        if( !entry )
            continue;

        // Multi parts per package: test if already visited:
        if( entry->GetPartCount() > 1 )
        {
            if( s_ReferencesAlreadyFound.Lookup( ref ) )
                continue;
        }

        if( entry->GetPartCount() <= 1 )   // One part per package
        {
            LIB_PIN_LIST pins;

            entry->GetPins( pins, comp->GetUnitSelection( path ), comp->m_Convert );

            for( size_t i = 0; i < pins.size(); i++ )
            {
                LIB_PIN* pin = pins[i];

                wxASSERT( pin->Type() == COMPONENT_PIN_DRAW_TYPE );

                AddPinToComponentPinList( comp, path, pin );
            }
        }
        else    // Multiple parts per package: Collect all parts and pins for
                // this reference
            FindAllsInstancesOfComponent( comp, entry, path );

        // Sort pins in s_SortedComponentPinList by pin number
        sort( s_SortedComponentPinList.begin(),
              s_SortedComponentPinList.end(), SortPinsByNum );

        // Remove duplicate Pins in s_SortedComponentPinList
        EraseDuplicatePins( s_SortedComponentPinList );

        return comp;
    }

    return NULL;
}


/* Return the net name for the pin Pin.
 *  Net name is:
 *  "?" if pin not connected
 *  "netname" for global net (like gnd, vcc ..
 *  "netname_sheetnumber" for the usual nets
 */
static wxString ReturnPinNetName( NETLIST_OBJECT* Pin, const wxString& DefaultFormatNetname )
{
    int      netcode = Pin->GetNet();
    wxString NetName;

    if( ( netcode == 0 ) || ( Pin->m_FlagOfConnection != PAD_CONNECT ) )
        return NetName;

    NETLIST_OBJECT* netref = Pin->m_NetNameCandidate;
    if( netref )
        NetName = netref->m_Label;

    if( !NetName.IsEmpty() )
    {
        // prefix non global labels names by the sheet path, to avoid names collisions
        if( ( netref->m_Type != NET_PINLABEL )
           && ( netref->m_Type != NET_GLOBLABEL ) )
        {
            wxString lnet = NetName;
            NetName = netref->m_SheetList.PathHumanReadable();

            // If sheet path is too long, use the time stamp name instead
            if( NetName.Length() > 32 )
                NetName = netref->m_SheetList.Path();
            NetName += lnet;
        }
    }
    else
    {
        NetName.Printf( DefaultFormatNetname.GetData(), netcode );
    }

    return NetName;
}


static wxXmlNode* Node( const wxString& name, const wxString& content=wxEmptyString )
{
    wxXmlNode* n = new wxXmlNode( 0, wxXML_ELEMENT_NODE, name );

    if( content.Len() > 0 )     // excludes wxEmptyString
        n->AddChild( new wxXmlNode( 0, wxXML_TEXT_NODE, wxEmptyString, content ) );

    return n;
}


/* Create a generic netlist, and call an external netlister
 *  to change the netlist syntax and create the file
 */
bool Write_GENERIC_NetList( WinEDA_SchematicFrame* frame, const wxString& aOutFileName )
{
#if 1
    // output the XML format netlist.
    wxXmlDocument   xdoc;
                                // tree markers or walkers
    wxXmlNode*      xroot;      // root node
    wxXmlNode*      xcomps;     // start of components

    wxString        timeStamp;

    // some strings we need many times, but don't want to construct more
    // than once for performance.  These are used within loops so the
    // enclosing wxString constructor would fire on each usage.
    const wxString  sFields     = wxT("fields");
    const wxString  sField      = wxT("field");
    const wxString  sComponent  = wxT("comp");      // use "part" ?
    const wxString  sName       = wxT("name");
    const wxString  sRef        = wxT("ref");
    const wxString  sPins       = wxT("pins");
    const wxString  sPin        = wxT("pin");
    const wxString  sValue      = wxT("value");
    const wxString  sFootprint  = wxT("footprint");
    const wxString  sDatasheet  = wxT("datasheet");
    const wxString  sTStamp     = wxT("tstamp");
    const wxString  sTSFmt      = wxT("%8.8lX");        // comp->m_TimeStamp

    s_ReferencesAlreadyFound.Clear();

    xdoc.SetRoot( xroot = Node( wxT("netlist") ) );
    xroot->AddProperty( wxT("version"), wxT("B") );

    xroot->AddChild( xcomps = Node( wxT("components") ) );

    SCH_SHEET_LIST sheetList;

    // output is xml, so there is no reason to remove spaces from the field values.

    for( SCH_SHEET_PATH* path = sheetList.GetFirst();  path;  path = sheetList.GetNext() )
    {
        for( EDA_BaseStruct* schItem = path->LastDrawList();  schItem;  schItem = schItem->Next() )
        {
            SCH_COMPONENT*  comp = FindNextComponentAndCreatPinList( schItem, path );
            if( !comp )
                break;  // No component left

            schItem = comp;

            // current component being constructed
            wxXmlNode* xcomp = Node( sComponent );
            xcomps->AddChild( xcomp );
            xcomp->AddProperty( sRef, comp->GetRef( path ) );

            xcomp->AddChild( Node( sValue, comp->GetField( VALUE )->m_Text ) );

            timeStamp.Printf( sTSFmt, comp->m_TimeStamp );

            xcomp->AddChild( Node( sTStamp, timeStamp ) );

            if( !comp->GetField( FOOTPRINT )->m_Text.IsEmpty() )
                xcomp->AddChild( Node( sFootprint, comp->GetField( FOOTPRINT )->m_Text ) );

            if( !comp->GetField( DATASHEET )->m_Text.IsEmpty() )
                xcomp->AddChild( Node( sDatasheet, comp->GetField( DATASHEET )->m_Text ) );

            // all fields within the component, starting with the MANDATORY_FIELDS
            if( comp->GetFieldCount() > MANDATORY_FIELDS )
            {
                wxXmlNode* xfields = Node( sFields );
                xcomp->AddChild( xfields );

                for( int fldNdx = MANDATORY_FIELDS;  fldNdx < comp->GetFieldCount();  ++fldNdx )
                {
                    SCH_FIELD*  f = comp->GetField( fldNdx );
                    wxXmlNode*  xfield = Node( sField, f->m_Text );

                    xfield->AddProperty( sName, f->m_Name );
                    xfields->AddChild( xfield );
                }
            }

            // @todo add:  libsource + sheetpath
        }
    }

    // @todo generate the nested <libpart> s
    xroot->AddChild( Node( wxT("libparts") ) );

    // @todo generate the nested <net>s
    xroot->AddChild( Node( wxT("nets") ) );

    return xdoc.Save( aOutFileName, 2 /* indent bug, today was ignored by wxXml lib */ );

#else

    // ouput the well established/old net list format
    wxString        field;
    wxString        footprint;
    wxString        netname;
    FILE*           out;
    int             ret = 0;    // OR on each call, test sign bit at very end.

    if( ( out = wxFopen( aOutFileName, wxT( "wt" ) ) ) == NULL )
    {
        wxString msg = _( "Failed to create file " ) + aOutFileName;
        DisplayError( frame, msg );
        return false;
    }

    s_ReferencesAlreadyFound.Clear();

    ret |= fprintf( out, "$BeginNetlist\n" );

    // Create netlist module section
    ret |= fprintf( out, "$BeginComponentList\n" );

    SCH_SHEET_LIST sheetList;

    for( SCH_SHEET_PATH* path = sheetList.GetFirst();  path;  path = sheetList.GetNext() )
    {
        for( EDA_BaseStruct* schItem = path->LastDrawList();  schItem;  schItem = schItem->Next() )
        {
            SCH_COMPONENT*  comp = FindNextComponentAndCreatPinList( schItem, path );
            if( !comp )
                break;  // No component left

            schItem = comp;

            footprint.Empty();
            if( !comp->GetField( FOOTPRINT )->IsVoid() )
            {
                footprint = comp->GetField( FOOTPRINT )->m_Text;
                footprint.Replace( wxT( " " ), wxT( "_" ) );
            }

            ret |= fprintf( out, "\n$BeginComponent\n" );
            ret |= fprintf( out, "TimeStamp=%8.8lX\n", comp->m_TimeStamp );
            ret |= fprintf( out, "Footprint=%s\n", CONV_TO_UTF8( footprint ) );

            field = wxT( "Reference=" ) + comp->GetRef( path ) + wxT( "\n" );
            field.Replace( wxT( " " ), wxT( "_" ) );
            ret |= fputs( CONV_TO_UTF8( field ), out );

            field = comp->GetField( VALUE )->m_Text;
            field.Replace( wxT( " " ), wxT( "_" ) );
            ret |= fprintf( out, "Value=%s\n", CONV_TO_UTF8( field ) );

            field = comp->m_ChipName;
            field.Replace( wxT( " " ), wxT( "_" ) );
            ret |= fprintf( out, "Libref=%s\n", CONV_TO_UTF8( field ) );

            // Write pin list:
            ret |= fprintf( out, "$BeginPinList\n" );
            for( unsigned ii = 0; ii < s_SortedComponentPinList.size(); ii++ )
            {
                NETLIST_OBJECT* Pin = s_SortedComponentPinList[ii];
                if( !Pin )
                    continue;

                netname = ReturnPinNetName( Pin, wxT( "$-%.6d" ) );
                if( netname.IsEmpty() )
                    netname = wxT( "?" );

                ret |= fprintf( out, "%.4s=%s\n", (char*) &Pin->m_PinNum, CONV_TO_UTF8( netname ) );
            }

            ret |= fprintf( out, "$EndPinList\n" );
            ret |= fprintf( out, "$EndComponent\n" );
        }
    }

    ret |= fprintf( out, "$EndComponentList\n" );

    ret |= fprintf( out, "\n$BeginNets\n" );

    if( !WriteGENERICListOfNets( out, g_NetObjectslist ) )
        ret = -1;

    ret |= fprintf( out, "$EndNets\n" );

    ret |= fprintf( out, "\n$EndNetlist\n" );
    ret |= fclose( out );

    return ret >= 0;
#endif
}


/* Routine generation of the netlist file (Format PSPICE)
 * = TRUE if use_netnames
 * Nodes are identified by the netname
 * If the nodes are identified by the netnumber
 *
 * All graphics text commentary by a [.-+] PSpice or [.-+] gnucap
 * Are considered in placing orders in the netlist
 * [.-] Or PSpice gnucap are beginning
 * + + Gnucap and PSpice are ultimately NetList
 */
static void WriteNetListPspice( WinEDA_SchematicFrame* frame, FILE* f, bool use_netnames )
{
    char            Line[1024];
    SCH_SHEET_PATH* sheet;
    EDA_BaseStruct* DrawList;
    SCH_COMPONENT*  Component;
    int             nbitems;
    wxString        text;
    wxArrayString   SpiceCommandAtBeginFile, SpiceCommandAtEndFile;
    wxString        msg;

#define BUFYPOS_LEN 4
    wxChar          bufnum[BUFYPOS_LEN + 1];

    DateAndTime( Line );
    fprintf( f,
             "* %s (Spice format) creation date: %s\n\n",
             NETLIST_HEAD_STRING,
             Line );

    /* Create text list starting by [.-]pspice , or [.-]gnucap (simulator
     * commands) and create text list starting by [+]pspice , or [+]gnucap
     * (simulator commands) */
    bufnum[BUFYPOS_LEN] = 0;
    SCH_SHEET_LIST SheetList;

    for( sheet = SheetList.GetFirst();
        sheet != NULL;
        sheet = SheetList.GetNext() )
    {
        for( DrawList = sheet->LastDrawList();
            DrawList != NULL;
            DrawList = DrawList->Next() )
        {
            wxChar ident;
            if( DrawList->Type() != TYPE_SCH_TEXT )
                continue;
            #define DRAWTEXT ( (SCH_TEXT*) DrawList )
            text = DRAWTEXT->m_Text; if( text.IsEmpty() )
                continue;
            ident = text.GetChar( 0 );
            if( ident != '.' && ident != '-' && ident != '+' )
                continue;
            text.Remove( 0, 1 );    // Remove the first char.
            text.Remove( 6 );       // text contains 6 char.
            text.MakeLower();
            if( ( text == wxT( "pspice" ) ) || ( text == wxT( "gnucap" ) ) )
            {
                /* Put the Y position as an ascii string, for sort by vertical
                 * position, using usual sort string by alphabetic value */
                int ypos = DRAWTEXT->m_Pos.y;
                for( int ii = 0; ii < BUFYPOS_LEN; ii++ )
                {
                    bufnum[BUFYPOS_LEN - 1 -
                           ii] = (ypos & 63) + ' '; ypos >>= 6;
                }

                text = DRAWTEXT->m_Text.AfterFirst( ' ' );

                // First BUFYPOS_LEN char are the Y position.
                msg.Printf( wxT( "%s %s" ), bufnum, text.GetData() );
                if( ident == '+' )
                    SpiceCommandAtEndFile.Add( msg );
                else
                    SpiceCommandAtBeginFile.Add( msg );
            }
        }
    }

    /* Print texts starting by [.-]pspice , ou [.-]gnucap (of course, without
     * the Y position string)*/
    nbitems = SpiceCommandAtBeginFile.GetCount();
    if( nbitems )
    {
        SpiceCommandAtBeginFile.Sort();
        for( int ii = 0; ii < nbitems; ii++ )
        {
            SpiceCommandAtBeginFile[ii].Remove( 0, BUFYPOS_LEN );
            SpiceCommandAtBeginFile[ii].Trim( TRUE );
            SpiceCommandAtBeginFile[ii].Trim( FALSE );
            fprintf( f, "%s\n", CONV_TO_UTF8( SpiceCommandAtBeginFile[ii] ) );
        }
    }
    fprintf( f, "\n" );

    //  Create component list

    s_ReferencesAlreadyFound.Clear();

    for( sheet = SheetList.GetFirst(); sheet != NULL; sheet = SheetList.GetNext() )
    {
        for( DrawList = sheet->LastDrawList(); DrawList != NULL; DrawList = DrawList->Next() )
        {
            DrawList = Component = FindNextComponentAndCreatPinList( DrawList, sheet );
            if( Component == NULL )
                break;

            fprintf( f, "%s ", CONV_TO_UTF8( Component->GetRef( sheet ) ) );

            // Write pin list:
            for( unsigned ii = 0; ii < s_SortedComponentPinList.size(); ii++ )
            {
                NETLIST_OBJECT* Pin = s_SortedComponentPinList[ii];
                if( !Pin )
                    continue;
                wxString        NetName = ReturnPinNetName( Pin, wxT( "N-%.6d" ) );
                if( NetName.IsEmpty() )
                    NetName = wxT( "?" );
                if( use_netnames )
                    fprintf( f, " %s", CONV_TO_UTF8( NetName ) );
                else    // Use number for net names (with net number = 0 for
                        // "GND"
                {
                    // NetName = "0" is "GND" net for Spice
                    if( NetName == wxT( "0" ) || NetName == wxT( "GND" ) )
                        fprintf( f, " 0" );
                    else
                        fprintf( f, " %d", Pin->GetNet() );
                }
            }

            fprintf( f, " %s\n",
                    CONV_TO_UTF8( Component->GetField( VALUE )->m_Text ) );
        }
    }

    s_SortedComponentPinList.clear();

    /* Print texts starting by [+]pspice , ou [+]gnucap */
    nbitems = SpiceCommandAtEndFile.GetCount();
    if( nbitems )
    {
        fprintf( f, "\n" );
        SpiceCommandAtEndFile.Sort();
        for( int ii = 0; ii < nbitems; ii++ )
        {
            SpiceCommandAtEndFile[ii].Remove( 0, +BUFYPOS_LEN );
            SpiceCommandAtEndFile[ii].Trim( TRUE );
            SpiceCommandAtEndFile[ii].Trim( FALSE );
            fprintf( f, "%s\n", CONV_TO_UTF8( SpiceCommandAtEndFile[ii] ) );
        }
    }

    fprintf( f, "\n.end\n" );
}


/* Generate net list file (Format 2 improves ORCAD PCB)
 * = TRUE if with_pcbnew
 * Format Pcbnew (OrcadPcb2 + reviews and lists of net)
 * = FALSE if with_pcbnew
 * Format ORCADPCB2 strict
 */
static bool WriteNetListPCBNEW( WinEDA_SchematicFrame* frame, FILE* f, bool with_pcbnew )
{
    wxString    Line;
    wxString    footprint;
    char        dateBuf[256];
    int         ret = 0;        // zero now, OR in the sign bit on error

    std::vector<OBJ_CMP_TO_LIST> cmpList;

    DateAndTime( dateBuf );

    if( with_pcbnew )
        ret |= fprintf( f, "# %s created  %s\n(\n", NETLIST_HEAD_STRING, dateBuf );
    else
        ret |= fprintf( f, "( { %s created  %s }\n", NETLIST_HEAD_STRING, dateBuf );

    // Create netlist module section

    s_ReferencesAlreadyFound.Clear();

    SCH_SHEET_LIST sheetList;

    for( SCH_SHEET_PATH* path = sheetList.GetFirst();  path;  path = sheetList.GetNext() )
    {
        for( EDA_BaseStruct* item = path->LastDrawList();  item;  item = item->Next() )
        {
            SCH_COMPONENT* comp = FindNextComponentAndCreatPinList( item, path );
            if( !comp )
                break;

            item = comp;

            // Get the Component FootprintFilter and put the component in
            // cmpList if filter is present
            LIB_COMPONENT* entry =
                CMP_LIBRARY::FindLibraryComponent( comp->m_ChipName );

            if( entry )
            {
                if( entry->m_FootprintList.GetCount() != 0 )    // Put in list
                {
                    cmpList.push_back( OBJ_CMP_TO_LIST() );

                    cmpList.back().m_RootCmp = comp;
                    cmpList.back().SetRef( comp->GetRef( path ) );
                }
            }

            if( !comp->GetField( FOOTPRINT )->IsVoid() )
            {
                footprint = comp->GetField( FOOTPRINT )->m_Text;
                footprint.Replace( wxT( " " ), wxT( "_" ) );
            }
            else
                footprint = wxT( "$noname" );

            Line = comp->GetRef( path );

            ret |= fprintf( f, " ( %s %s",
                    CONV_TO_UTF8( comp->GetPath( path ) ),
                    CONV_TO_UTF8( footprint ) );

            ret |= fprintf( f, "  %s", CONV_TO_UTF8( Line ) );

            Line = comp->GetField( VALUE )->m_Text;
            Line.Replace( wxT( " " ), wxT( "_" ) );
            ret |= fprintf( f, " %s", CONV_TO_UTF8( Line ) );

            if( with_pcbnew )  // Add the lib name for this component
            {
                Line = comp->m_ChipName;
                Line.Replace( wxT( " " ), wxT( "_" ) );
                ret |= fprintf( f, " {Lib=%s}", CONV_TO_UTF8( Line ) );
            }
            ret |= fprintf( f, "\n" );

            // Write pin list:
            for( unsigned ii = 0; ii < s_SortedComponentPinList.size(); ii++ )
            {
                NETLIST_OBJECT* pin = s_SortedComponentPinList[ii];
                if( !pin )
                    continue;

                wxString netname = ReturnPinNetName( pin, wxT( "N-%.6d" ) );
                if( netname.IsEmpty() )
                    netname = wxT( "?" );

                netname.Replace( wxT( " " ), wxT( "_" ) );

                ret |= fprintf( f, "  ( %4.4s %s )\n", (char*) &pin->m_PinNum,
                        CONV_TO_UTF8( netname ) );
            }

            ret |= fprintf( f, " )\n" );
        }
    }

    ret |= fprintf( f, ")\n*\n" );

    s_SortedComponentPinList.clear();

    // Write the allowed footprint list for each component
    if( with_pcbnew && cmpList.size() )
    {
        wxString    ref;

        ret |= fprintf( f, "{ Allowed footprints by component:\n" );
        for( unsigned ii = 0; ii < cmpList.size(); ii++ )
        {
            SCH_COMPONENT* comp = cmpList[ii].m_RootCmp;

            LIB_COMPONENT* entry = CMP_LIBRARY::FindLibraryComponent( comp->m_ChipName );

            ref = cmpList[ii].GetRef();

            ref.Replace( wxT( " " ), wxT( "_" ) );

            ret |= fprintf( f, "$component %s\n", CONV_TO_UTF8( ref ) );

            // Write the footprint list
            for( unsigned jj = 0; jj < entry->m_FootprintList.GetCount(); jj++ )
            {
                ret |= fprintf( f, " %s\n",
                        CONV_TO_UTF8( entry->m_FootprintList[jj] ) );
            }

            ret |= fprintf( f, "$endlist\n" );
        }

        ret |= fprintf( f, "$endfootprintlist\n}\n" );
    }

    if( with_pcbnew )
    {
        ret |= fprintf( f, "{ Pin List by Nets\n" );

        if( !WriteGENERICListOfNets( f, g_NetObjectslist ) )
            ret = -1;

        ret |= fprintf( f, "}\n" );
        ret |= fprintf( f, "#End\n" );
    }

    return ret >= 0;
}


/*
 * Add a new pin description in the pin list s_SortedComponentPinList
 * a pin description is a pointer to the corresponding structure
 * created by BuildNetList() in the table g_NetObjectslist
 */
static void AddPinToComponentPinList( SCH_COMPONENT* Component,
                                      SCH_SHEET_PATH* sheetlist, LIB_PIN* Pin )
{
    // Search the PIN description for Pin in g_NetObjectslist
    for( unsigned ii = 0; ii < g_NetObjectslist.size(); ii++ )
    {
        if( g_NetObjectslist[ii]->m_Type != NET_PIN )
            continue;
        if( g_NetObjectslist[ii]->m_Link != Component )
            continue;
        if( g_NetObjectslist[ii]->m_SheetList != *sheetlist )
            continue;
        if( g_NetObjectslist[ii]->m_PinNum != Pin->m_PinNum )
            continue;

        s_SortedComponentPinList.push_back( g_NetObjectslist[ii] );

        if( s_SortedComponentPinList.size() >= MAXPIN )
        {
            // Log message for Internal error
            DisplayError( NULL, wxT( "AddPinToComponentPinList err: MAXPIN reached" ) );
            return;
        }
    }
}


/** Function EraseDuplicatePins
 *  Function to remove duplicate Pins in the TabPin pin list
 *  (This is a list of pins found in the whole schematic, for a given
 * component)
 *  These duplicate pins were put in list because some pins (powers... )
 *  are found more than one time when we have a multiple parts per package
 * component
 *  for instance, a 74ls00 has 4 parts, and therefore the VCC pin and GND pin
 * appears 4 times
 *  in the list.
 * @param aPinList = a NETLIST_OBJECT_LIST that contains the list of pins for a
 * given component.
 * Note: this list *MUST* be sorted by pin number (.m_PinNum member value)
 */
static void EraseDuplicatePins( NETLIST_OBJECT_LIST& aPinList )
{
    if( aPinList.size() == 0 )  // Trivial case: component with no pin
        return;

    for( unsigned ii = 0; ii < aPinList.size(); ii++ )
    {
        if( aPinList[ii] == NULL ) /* already deleted */
            continue;

        /* Search for duplicated pins
         * If found, remove duplicates. The priority is to keep connected pins
         * and remove unconnected
         * - So this allows (for instance when using multi op amps per package
         * - to connect only one op amp to power
         * Because the pin list is sorted by m_PinNum value, duplicated pins
         * are necessary successive in list
         */
        int idxref = ii;
        for( unsigned jj = ii + 1; jj < aPinList.size(); jj++ )
        {
            if(  aPinList[jj] == NULL )   // Already removed
                continue;

            // other pin num end of duplicate list.
            if( aPinList[idxref]->m_PinNum != aPinList[jj]->m_PinNum )
                break;
            if( aPinList[idxref]->m_FlagOfConnection == PAD_CONNECT )
                aPinList[jj] = NULL;
            else /* the reference pin is not connected: remove this pin if the
                  * other pin is connected */
            {
                if( aPinList[jj]->m_FlagOfConnection == PAD_CONNECT )
                {
                    aPinList[idxref] = NULL;
                    idxref = jj;
                }
                else    // the 2 pins are not connected: remove the tested pin,
                        // and continue ...
                    aPinList[jj] = NULL;
            }
        }
    }
}


/**
 * Used for multiple parts per package components.
 *
 * Search all instances of Component_in,
 * Calls AddPinToComponentPinList() to and pins founds to the current
 * component pin list
 */
static void FindAllsInstancesOfComponent( SCH_COMPONENT*  Component_in,
                                          LIB_COMPONENT*  aEntry,
                                          SCH_SHEET_PATH* Sheet_in )
{
    EDA_BaseStruct* SchItem;
    SCH_COMPONENT* Component2;
    LIB_PIN* pin;
    SCH_SHEET_PATH* sheet;
    wxString str, Reference = Component_in->GetRef( Sheet_in );

    SCH_SHEET_LIST SheetList;

    for( sheet = SheetList.GetFirst(); sheet != NULL; sheet = SheetList.GetNext() )
    {
        for( SchItem = sheet->LastDrawList(); SchItem; SchItem = SchItem->Next() )
        {
            if( SchItem->Type() != TYPE_SCH_COMPONENT )
                continue;

            Component2 = (SCH_COMPONENT*) SchItem;

            str = Component2->GetRef( sheet );
            if( str.CmpNoCase( Reference ) != 0 )
                continue;

            if( aEntry == NULL )
                continue;

            for( pin = aEntry->GetNextPin(); pin != NULL; pin = aEntry->GetNextPin( pin ) )
            {
                wxASSERT( pin->Type() == COMPONENT_PIN_DRAW_TYPE );

                if( pin->m_Unit
                   && ( pin->m_Unit != Component2->GetUnitSelection( sheet ) ) )
                    continue;

                if( pin->m_Convert
                   && ( pin->m_Convert != Component2->m_Convert ) )
                    continue;

                // A suitable pin in found: add it to the current list
                AddPinToComponentPinList( Component2, sheet, pin );
            }
        }
    }
}


/*
 * Comparison routine for sorting by pin numbers.
 */
static bool SortPinsByNum( NETLIST_OBJECT* Pin1, NETLIST_OBJECT* Pin2 )
{
    int Num1, Num2;
    char Line[5];

    Num1    = Pin1->m_PinNum;
    Num2    = Pin2->m_PinNum;
    Line[4] = 0;
    memcpy( Line, &Num1, 4 ); Num1 = atoi( Line );
    memcpy( Line, &Num2, 4 ); Num2 = atoi( Line );
    return Num1 < Num2;
}


/* Written in the file / net list (ranked by Netcode), and elements that are
 * connected
 */
static bool WriteGENERICListOfNets( FILE* f, NETLIST_OBJECT_LIST& aObjectsList )
{
    int         ret = 0;
    int         netCode;
    int         lastNetCode = -1;
    int         sameNetcodeCount = 0;
    wxString    netName;
    wxString    ref;
    wxString    netcodeName;
    char        firstItemInNet[256];

    for( unsigned ii = 0; ii < aObjectsList.size(); ii++ )
    {
        SCH_COMPONENT*  comp;

        // New net found, write net id;
        if( ( netCode = aObjectsList[ii]->GetNet() ) != lastNetCode )
        {
            sameNetcodeCount = 0;              // Items count for this net
            netName.Empty();

            // Find a label (if exists) for this net.
            NETLIST_OBJECT* netref;
            netref = aObjectsList[ii]->m_NetNameCandidate;
            if( netref )
                netName = netref->m_Label;

            netcodeName.Printf( wxT( "Net %d " ), netCode );
            netcodeName += wxT( "\"" );
            if( !netName.IsEmpty() )
            {
                if( ( netref->m_Type != NET_PINLABEL )
                   && ( netref->m_Type != NET_GLOBLABEL ) )
                {
                    // usual net name, prefix it by the sheet path
                    netcodeName += netref->m_SheetList.PathHumanReadable();
                }
                netcodeName += netName;
            }
            netcodeName += wxT( "\"" );

            // Add the netname without prefix, in cases we need only the
            // "short" netname
            netcodeName += wxT( " \"" ) + netName + wxT( "\"" );
            lastNetCode  = netCode;
        }

        if( aObjectsList[ii]->m_Type != NET_PIN )
            continue;

        comp = (SCH_COMPONENT*) aObjectsList[ii]->m_Link;

        // Get the reference for the net name and the main parent component
        ref = comp->GetRef( &aObjectsList[ii]->m_SheetList );
        if( ref[0] == wxChar( '#' ) )
            continue;                 // Pseudo component (Like Power symbol)

        // Print the pin list for this net, use special handling if
        // 2 or more items are connected:

        // if first item for this net found, defer printing this connection
        // until a second item will is found
        if( ++sameNetcodeCount == 1 )
        {
            snprintf( firstItemInNet, sizeof(firstItemInNet), " %s %.4s\n",
                      CONV_TO_UTF8( ref ),
                      (const char*) &aObjectsList[ii]->m_PinNum );
        }

        // Second item for this net found, print the Net name, and the
        // first item
        if( sameNetcodeCount == 2 )
        {
            ret |= fprintf( f, "%s\n", CONV_TO_UTF8( netcodeName ) );
            ret |= fputs( firstItemInNet, f );
        }

        if( sameNetcodeCount >= 2 )
            ret |= fprintf( f, " %s %.4s\n", CONV_TO_UTF8( ref ),
                     (const char*) &aObjectsList[ii]->m_PinNum );
    }

    return ret >= 0;
}


/* Generate CADSTAR net list. */
wxString StartLine( wxT( "." ) );


/* Routine generation of the netlist file (CADSTAR Format)
 * Header:
 * HEA ..
 * TIM .. 2004 07 29 16 22 17
 * APA .. "Cadstar RINF Output - Version 6.0.2.3"
 * INCH UNI .. 1000.0 in
 * FULL TYP ..
 *
 * List of components:
 * .. ADD_COM X1 "CNT D41612 (48pts CONTOUR TM)"
 * .. ADD_COM U2 "74HCT245D" "74HCT245D"
 *
 * Connections:
 *  .. ADD_TER RR2 * 6 "$ 42"
 * .. B U1 100
 * 6 CA
 *
 * ADD_TER .. U2 * 6 "$ 59"
 * .. B * U7 39
 * U6 17
 * U1 * 122
 *
 * .. ADD_TER P2 * 1 "$ 9"
 * .. B * T3 1
 *U1 * 14
 */
static void WriteNetListCADSTAR( WinEDA_SchematicFrame* frame, FILE* f )
{
    wxString StartCmpDesc = StartLine + wxT( "ADD_COM" );
    wxString msg;
    wxString footprint;
    char Line[1024];
    SCH_SHEET_PATH* sheet;
    EDA_BaseStruct* DrawList;
    SCH_COMPONENT* Component;
    wxString Title = wxGetApp().GetAppName() + wxT( " " ) + GetBuildVersion();

    fprintf( f, "%sHEA\n", CONV_TO_UTF8( StartLine ) );
    DateAndTime( Line );
    fprintf( f, "%sTIM %s\n", CONV_TO_UTF8( StartLine ), Line );
    fprintf( f, "%sAPP ", CONV_TO_UTF8( StartLine ) );
    fprintf( f, "\"%s\"\n", CONV_TO_UTF8( Title ) );
    fprintf( f, "\n" );

    // Create netlist module section
    s_ReferencesAlreadyFound.Clear();

    SCH_SHEET_LIST SheetList;

    for( sheet = SheetList.GetFirst(); sheet != NULL; sheet = SheetList.GetNext() )
    {
        for( DrawList = sheet->LastDrawList(); DrawList != NULL; DrawList = DrawList->Next() )
        {
            DrawList = Component = FindNextComponentAndCreatPinList( DrawList, sheet );
            if( Component == NULL )
                break;

            /*
            doing nothing with footprint
            if( !Component->GetField( FOOTPRINT )->IsVoid() )
            {
                footprint = Component->GetField( FOOTPRINT )->m_Text;
                footprint.Replace( wxT( " " ), wxT( "_" ) );
            }
            else
                footprint = wxT( "$noname" );
            */

            msg = Component->GetRef( sheet );
            fprintf( f, "%s     ", CONV_TO_UTF8( StartCmpDesc ) );
            fprintf( f, "%s", CONV_TO_UTF8( msg ) );

            msg = Component->GetField( VALUE )->m_Text;
            msg.Replace( wxT( " " ), wxT( "_" ) );
            fprintf( f, "     \"%s\"", CONV_TO_UTF8( msg ) );
            fprintf( f, "\n" );
        }
    }

    fprintf( f, "\n" );

    s_SortedComponentPinList.clear();

    WriteListOfNetsCADSTAR( f, g_NetObjectslist );

    fprintf( f, "\n%sEND\n", CONV_TO_UTF8( StartLine ) );
}


/*
 * Written in the file / net list (ranked by Netcode), and
 * Pins connected to it
 * Format:
 *. ADD_TER RR2 6 "$ 42"
 *. B U1 100
 * 6 CA
 */
static void WriteListOfNetsCADSTAR( FILE* f, NETLIST_OBJECT_LIST& aObjectsList )
{
    wxString InitNetDesc  = StartLine + wxT( "ADD_TER" );
    wxString StartNetDesc = StartLine + wxT( "TER" );
    wxString netcodeName, InitNetDescLine;
    unsigned ii;
    int print_ter = 0;
    int NetCode, lastNetCode = -1;
    SCH_COMPONENT* Cmp;
    wxString NetName;

    for( ii = 0; ii < aObjectsList.size(); ii++ )
        aObjectsList[ii]->m_Flag = 0;

    for( ii = 0; ii < g_NetObjectslist.size(); ii++ )
    {
        // Get the NetName of the current net :
        if( ( NetCode = aObjectsList[ii]->GetNet() ) != lastNetCode )
        {
            NetName.Empty();

            NETLIST_OBJECT* netref;
            netref = aObjectsList[ii]->m_NetNameCandidate;
            if( netref )
                NetName = netref->m_Label;

            netcodeName = wxT( "\"" );
            if( !NetName.IsEmpty() )
            {
                if( ( netref->m_Type != NET_PINLABEL )
                   && ( netref->m_Type != NET_GLOBLABEL ) )
                {
                    // usual net name, prefix it by the sheet path
                    netcodeName +=
                        netref->m_SheetList.PathHumanReadable();
                }
                netcodeName += NetName;
            }
            else  // this net has no name: create a default name $<net number>
                netcodeName << wxT( "$" ) << NetCode;
            netcodeName += wxT( "\"" );
            lastNetCode  = NetCode;
            print_ter    = 0;
        }


        if( aObjectsList[ii]->m_Type != NET_PIN )
            continue;

        if( aObjectsList[ii]->m_Flag != 0 )
            continue;

        Cmp = (SCH_COMPONENT*) aObjectsList[ii]->m_Link;
        wxString refstr = Cmp->GetRef( &(aObjectsList[ii]->m_SheetList) );
        if( refstr[0] == '#' )
            continue;  // Power supply symbols.

        switch( print_ter )
        {
        case 0:
        {
            char buf[5];
            wxString str_pinnum;
            strncpy( buf, (char*) &aObjectsList[ii]->m_PinNum, 4 );
            buf[4]     = 0;
            str_pinnum = CONV_FROM_UTF8( buf );
            InitNetDescLine.Printf( wxT( "\n%s   %s   %.4s     %s" ),
                                   GetChars( InitNetDesc ),
                                   GetChars( refstr ),
                                   GetChars( str_pinnum ),
                                   GetChars( netcodeName ) );
        }
            print_ter++;
            break;

        case 1:
            fprintf( f, "%s\n", CONV_TO_UTF8( InitNetDescLine ) );
            fprintf( f, "%s       %s   %.4s\n",
                     CONV_TO_UTF8( StartNetDesc ),
                     CONV_TO_UTF8( refstr ),
                     (char*) &aObjectsList[ii]->m_PinNum );
            print_ter++;
            break;

        default:
            fprintf( f, "            %s   %.4s\n",
                     CONV_TO_UTF8( refstr ),
                     (char*) &aObjectsList[ii]->m_PinNum );
            break;
        }

        aObjectsList[ii]->m_Flag = 1;

        // Search for redundant pins to avoid generation of the same connection
        // more than once.
        for( unsigned jj = ii + 1; jj < aObjectsList.size(); jj++ )
        {
            if( aObjectsList[jj]->GetNet() != NetCode )
                break;
            if( aObjectsList[jj]->m_Type != NET_PIN )
                continue;
            SCH_COMPONENT* tstcmp =
                (SCH_COMPONENT*) aObjectsList[jj]->m_Link;
            wxString p    = Cmp->GetPath( &( aObjectsList[ii]->m_SheetList ) );
            wxString tstp = tstcmp->GetPath( &( aObjectsList[jj]->m_SheetList ) );
            if( p.Cmp( tstp ) != 0 )
                continue;

            if( aObjectsList[jj]->m_PinNum == aObjectsList[ii]->m_PinNum )
                aObjectsList[jj]->m_Flag = 1;
        }
    }
}
