/*
 * This program source code file is part of KiCad, a free EDA CAD application.
 *
 * Copyright (C) 2014-2017  Cirilo Bernardo
 * Copyright (C) 2021 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
 */


#include <iostream>
#include <iomanip>
#include <sstream>
#include <cmath>
#include <cerrno>
#include <algorithm>
#include <utility>
#include <wx/string.h>
#include <wx/filename.h>

#include "idf_parser.h"
#include "idf_helpers.h"
#include "streamwrapper.h"

using namespace std;
using namespace IDF3;


static bool MatchCompOutline( IDF3_COMP_OUTLINE* aOutlineA, IDF3_COMP_OUTLINE* aOutlineB )
{
    if( aOutlineA->GetComponentClass() != aOutlineB->GetComponentClass() )
        return false;

    if( aOutlineA->OutlinesSize() != aOutlineB->OutlinesSize() )
        return false;

    // are both outlines empty?
    if( aOutlineA->OutlinesSize() == 0 )
        return true;

    IDF_OUTLINE* opA = aOutlineA->GetOutline( 0 );
    IDF_OUTLINE* opB = aOutlineB->GetOutline( 0 );

    if( opA->size() != opB->size() )
        return false;

    if( opA->size() == 0 )
        return true;

    std::list<IDF_SEGMENT*>::iterator olAs = opA->begin();
    std::list<IDF_SEGMENT*>::iterator olAe = opA->end();
    std::list<IDF_SEGMENT*>::iterator olBs = opB->begin();

    while( olAs != olAe )
    {
        if( !(*olAs)->MatchesStart( (*olBs)->startPoint ) )
            return false;

        if( !(*olAs)->MatchesEnd( (*olBs)->endPoint ) )
            return false;

        ++olAs;
        ++olBs;
    }

    return true;
}


IDF3_COMP_OUTLINE_DATA::IDF3_COMP_OUTLINE_DATA()
{
    parent = nullptr;
    outline = nullptr;
    xoff = 0.0;
    yoff = 0.0;
    zoff = 0.0;
    aoff = 0.0;
}


IDF3_COMP_OUTLINE_DATA::IDF3_COMP_OUTLINE_DATA( IDF3_COMPONENT* aParent,
                                                IDF3_COMP_OUTLINE* aOutline )
{
    parent = aParent;
    outline = aOutline;
    xoff = 0.0;
    yoff = 0.0;
    zoff = 0.0;
    aoff = 0.0;

    if( aOutline )
        aOutline->incrementRef();
}


IDF3_COMP_OUTLINE_DATA::IDF3_COMP_OUTLINE_DATA( IDF3_COMPONENT* aParent,
                                                IDF3_COMP_OUTLINE* aOutline,
                                                double aXoff, double aYoff,
                                                double aZoff, double aAngleOff )
{
    parent = aParent;
    outline = aOutline;
    xoff = aXoff;
    yoff = aYoff;
    zoff = aZoff;
    aoff = aAngleOff;
}


IDF3_COMP_OUTLINE_DATA::~IDF3_COMP_OUTLINE_DATA()
{
    if( outline )
        outline->decrementRef();
}


#ifndef DISABLE_IDF_OWNERSHIP
bool IDF3_COMP_OUTLINE_DATA::checkOwnership( int aSourceLine, const char* aSourceFunc )
{
    if( !parent )
    {
        ostringstream ostr;
        ostr << __FILE__ << ":" << aSourceLine << ":" << aSourceFunc << "():\n";
        ostr << "* BUG: IDF3_COMP_OUTLINE_DATA::parent not set; cannot enforce ownership rules\n";
        errormsg = ostr.str();

        return false;
    }

    IDF3::IDF_PLACEMENT placement = parent->GetPlacement();
    IDF3::CAD_TYPE parentCAD = parent->GetCadType();

    if( placement == PS_PLACED || placement == PS_UNPLACED )
        return true;

    if( placement == PS_MCAD && parentCAD == CAD_MECH )
        return true;

    if( placement == PS_ECAD && parentCAD == CAD_ELEC )
        return true;

    do
    {
        ostringstream ostr;
        ostr << "* " << __FILE__ << ":" << aSourceLine << ":" << aSourceFunc << "():\n";
        ostr << "* ownership violation; CAD type is ";

        if( parentCAD == CAD_MECH )
            ostr << "MCAD ";
        else
            ostr << "ECAD ";

        ostr << "while outline owner is " << GetPlacementString( placement ) << "\n";
        errormsg = ostr.str();

    } while( 0 );

    return false;
}
#endif


bool IDF3_COMP_OUTLINE_DATA::SetOffsets( double aXoff, double aYoff,
                                         double aZoff, double aAngleOff )
{
#ifndef DISABLE_IDF_OWNERSHIP
    if( !checkOwnership( __LINE__, __FUNCTION__ ) )
        return false;
#endif

    xoff = aXoff;
    yoff = aYoff;
    zoff = aZoff;
    aoff = aAngleOff;
    return true;
}


void IDF3_COMP_OUTLINE_DATA::GetOffsets( double& aXoff, double& aYoff,
                                         double& aZoff, double& aAngleOff )
{
    aXoff = xoff;
    aYoff = yoff;
    aZoff = zoff;
    aAngleOff = aoff;
}


void IDF3_COMP_OUTLINE_DATA::SetParent( IDF3_COMPONENT* aParent )
{
    parent = aParent;
}


bool IDF3_COMP_OUTLINE_DATA::SetOutline( IDF3_COMP_OUTLINE* aOutline )
{
#ifndef DISABLE_IDF_OWNERSHIP
    if( !checkOwnership( __LINE__, __FUNCTION__ ) )
        return false;
#endif

    if( outline )
        outline->decrementRef();

    outline = aOutline;

    if( outline )
        outline->incrementRef();

    return true;
}


bool IDF3_COMP_OUTLINE_DATA::readPlaceData( std::istream &aBoardFile,
                                            IDF3::FILE_STATE& aBoardState,
                                            IDF3_BOARD *aBoard,
                                            IDF3::IDF_VERSION aIdfVersion,
                                            bool aNoSubstituteOutlines )
{
    if( !aBoard )
        throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
                          "\n* BUG: invoked with no reference to the parent IDF_BOARD" ) );

    // clear out data possibly left over from previous use of the object
    outline = nullptr;
    parent  = nullptr;

    std::string iline;      // the input line
    bool isComment;         // true if a line just read in is a comment line
    std::streampos pos;
    int idx = 0;
    bool quoted = false;
    std::string token;
    std::string uid;
    std::string refdes;
    IDF3::IDF_PLACEMENT placement = IDF3::PS_UNPLACED;
    IDF3::IDF_LAYER side = IDF3::LYR_TOP;

    // RECORD 2: 'package name', 'part number', 'Refdes' (any, NOREFDES, BOARD)
    while( !FetchIDFLine( aBoardFile, iline, isComment, pos ) && aBoardFile.good() );

    if( ( !aBoardFile.good() && !aBoardFile.eof() ) || iline.empty() )
    {
        ostringstream ostr;

        ostr << "invalid IDF file\n";
        ostr << "* violation: could not read PLACEMENT section\n";
        ostr << "* file position: " << pos;

        throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) );
    }

    if( isComment )
    {
        ostringstream ostr;

        ostr << "invalid IDF file\n";
        ostr << "* violation: comment within PLACEMENT section\n";
        ostr << "* line: '" << iline << "'\n";
        ostr << "* file position: " << pos;

        throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) );
    }

    idx = 0;
    GetIDFString( iline, token, quoted, idx );

    if( !quoted && CompareToken( ".END_PLACEMENT", token ) )
    {
        aBoardState = IDF3::FILE_PLACEMENT;
        return false;
    }

    std::string ngeom = token;

    if( !GetIDFString( iline, token, quoted, idx ) )
    {
        ostringstream ostr;

        ostr << "invalid IDF file\n";
        ostr << "* violation: no PART NAME in PLACEMENT RECORD2\n";
        ostr << "* line: '" << iline << "'\n";
        ostr << "* file position: " << pos;

        throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) );
    }

    std::string npart = token;
    uid = ngeom + "_" + npart;

    if( !GetIDFString( iline, token, quoted, idx ) )
    {
        ostringstream ostr;

        ostr << "invalid IDF file\n";
        ostr << "* violation: no REFDES in PLACEMENT RECORD2\n";
        ostr << "* line: '" << iline << "'\n";
        ostr << "* file position: " << pos;

        throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) );
    }

    if( CompareToken( "NOREFDES", token ) )
    {
        // according to the IDF3.0 specification, this is a
        // mechanical component. The specification is defective
        // since it is impossible to associate mechanical
        // components with their holes unless the mechanical
        // component is given a unique RefDes. This class of defect
        // is one reason IDF does not work well in faithfully
        // conveying information between ECAD and MCAD.
        refdes = aBoard->GetNewRefDes();
    }
    else if( CompareToken( "BOARD", token ) )
    {
        ostringstream ostr;

        ostr << "UNSUPPORTED FEATURE\n";
        ostr << "* RefDes is 'BOARD', indicating this is a PANEL FILE (not supported)\n";
        ostr << "* line: '" << iline << "'\n";
        ostr << "* file position: " << pos;

        throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) );
    }
    else if( CompareToken( "PANEL", token ) )
    {
        ostringstream ostr;

        ostr << "invalid IDF file\n";
        ostr << "* violation: RefDes in PLACEMENT RECORD2 is 'PANEL'\n";
        ostr << "* line: '" << iline << "'\n";
        ostr << "* file position: " << pos;

        throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) );
    }
    else if( token.empty() )
    {
        ostringstream ostr;

        ostr << "invalid IDF file\n";
        ostr << "* violation: empty RefDes string in PLACEMENT RECORD2\n";
        ostr << "* line: '" << iline << "'\n";
        ostr << "* file position: " << pos;

        throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) );
    }
    else
    {
        // note: perversely, spaces can be a valid RefDes
        refdes = token;
    }

    // V2: RECORD 3: X, Y, ROT, SIDE (top/bot), PLACEMENT (fixed, placed, unplaced)
    // V3: RECORD 3: X, Y, Z, ROT, SIDE (top/bot), PLACEMENT (placed, unplaced, mcad, ecad)
    while( !FetchIDFLine( aBoardFile, iline, isComment, pos ) && aBoardFile.good() );

    if( !aBoardFile.good() )
    {
        ostringstream ostr;

        ostr << "invalid IDF file\n";
        ostr << "* problems reading PLACEMENT SECTION, RECORD 3\n";
        ostr << "* file position: " << pos;

        throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) );
    }

    if( isComment )
    {
        ostringstream ostr;

        ostr << "invalid IDF file\n";
        ostr << "* violation: comment within PLACEMENT section\n";
        ostr << "* line: '" << iline << "'\n";
        ostr << "* file position: " << pos;

        throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) );
    }

    idx = 0;
    GetIDFString( iline, token, quoted, idx );

    if( quoted )
    {
        ostringstream ostr;

        ostr << "invalid IDF file\n";
        ostr << "* violation: X value must not be in quotes (PLACEMENT RECORD 3)\n";
        ostr << "* line: '" << iline << "'\n";
        ostr << "* file position: " << pos;

        throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) );
    }

    istringstream istr;
    istr.str( token );

    istr >> xoff;
    if( istr.fail() )
    {
        ostringstream ostr;

        ostr << "invalid IDF file\n";
        ostr << "* violation: X value is not numeric (PLACEMENT RECORD 3)\n";
        ostr << "* line: '" << iline << "'\n";
        ostr << "* file position: " << pos;

        throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) );
    }

    if( !GetIDFString( iline, token, quoted, idx ) )
    {
        ostringstream ostr;

        ostr << "invalid IDF file\n";
        ostr << "* violation: no Y value (PLACEMENT RECORD 3)\n";
        ostr << "* line: '" << iline << "'\n";
        ostr << "* file position: " << pos;

        throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) );
    }

    istr.clear();
    istr.str( token );

    istr >> yoff;
    if( istr.fail() )
    {
        ostringstream ostr;

        ostr << "invalid IDF file\n";
        ostr << "* violation: Y value is not numeric (PLACEMENT RECORD 3)\n";
        ostr << "* line: '" << iline << "'\n";
        ostr << "* file position: " << pos;

        throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) );
    }

    if( aIdfVersion > IDF_V2 )
    {
        if( !GetIDFString( iline, token, quoted, idx ) )
        {
            ostringstream ostr;

            ostr << "invalid IDFv3 file\n";
            ostr << "* violation: no Z value (PLACEMENT RECORD 3)\n";
            ostr << "* line: '" << iline << "'\n";
            ostr << "* file position: " << pos;

            throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) );
        }

        istr.clear();
        istr.str( token );

        istr >> zoff;
        if( istr.fail() )
        {
            ostringstream ostr;

            ostr << "invalid IDFv3 file\n";
            ostr << "* violation: Z value is not numeric (PLACEMENT RECORD 3)\n";
            ostr << "* line: '" << iline << "'\n";
            ostr << "* file position: " << pos;

            throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) );
        }
    }

    if( !GetIDFString( iline, token, quoted, idx ) )
    {
        ostringstream ostr;

        ostr << "invalid IDF file\n";
        ostr << "* violation: no rotation value (PLACEMENT RECORD 3)\n";
        ostr << "* line: '" << iline << "'\n";
        ostr << "* file position: " << pos;

        throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) );
    }

    istr.clear();
    istr.str( token );

    istr >> aoff;
    if( istr.fail() )
    {
        ostringstream ostr;

        ostr << "invalid IDF file\n";
        ostr << "* violation: rotation value is not numeric (PLACEMENT RECORD 3)\n";
        ostr << "* line: '" << iline << "'\n";
        ostr << "* file position: " << pos;

        throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) );
    }

    if( !GetIDFString( iline, token, quoted, idx ) )
    {
        ostringstream ostr;

        ostr << "invalid IDF file\n";
        ostr << "* violation: no SIDE value (PLACEMENT RECORD 3)\n";
        ostr << "* line: '" << iline << "'\n";
        ostr << "* file position: " << pos;

        throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) );
    }

    if( CompareToken( "TOP", token ) )
    {
        side = IDF3::LYR_TOP;
    }
    else if( CompareToken( "BOTTOM", token ) )
    {
        side = IDF3::LYR_BOTTOM;
    }
    else
    {
        ostringstream ostr;

        ostr << "invalid IDF file\n";
        ostr << "* violation: invalid SIDE value in PLACEMENT RECORD 3 ('";
        ostr << token << "'); must be one of TOP/BOTTOM\n";
        ostr << "* line: '" << iline << "'\n";
        ostr << "* file position: " << pos;

        throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) );
    }

    if( !GetIDFString( iline, token, quoted, idx ) )
    {
        ostringstream ostr;

        ostr << "invalid IDF file\n";
        ostr << "* violation: no PLACEMENT value in PLACEMENT RECORD 3\n";
        ostr << "* line: '" << iline << "'\n";
        ostr << "* file position: " << pos;

        throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) );
    }

    if( CompareToken( "PLACED", token ) )
    {
        placement = IDF3::PS_PLACED;
    }
    else if( CompareToken( "UNPLACED", token ) )
    {
        placement = IDF3::PS_UNPLACED;
    }
    else if( aIdfVersion > IDF_V2 && CompareToken( "MCAD", token ) )
    {
        placement = IDF3::PS_MCAD;
    }
    else if( aIdfVersion > IDF_V2 && CompareToken( "ECAD", token ) )
    {
        placement = IDF3::PS_ECAD;
    }
    else if( aIdfVersion < IDF_V3 && CompareToken( "FIXED", token ) )
    {
        if( aBoard->GetCadType() == CAD_ELEC )
            placement = IDF3::PS_MCAD;
        else
            placement = IDF3::PS_ECAD;
    }
    else
    {
        ostringstream ostr;

        ostr << "invalid IDF file\n";
        ostr << "* violation: invalid PLACEMENT value ('";
        ostr << token << "') in PLACEMENT RECORD 3\n";
        ostr << "* line: '" << iline << "'\n";
        ostr << "* file position: " << pos;

        throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) );
    }

    outline = aBoard->GetComponentOutline( uid );

    if( outline == nullptr && !aNoSubstituteOutlines )
    {
        ERROR_IDF << "MISSING OUTLINE\n";
        cerr << "* GeomName( " << ngeom << " ), PartName( " << npart << " )\n";
        cerr << "* Substituting default outline.\n";
        outline = aBoard->GetInvalidOutline( ngeom, npart );

        if( outline == nullptr )
            throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
                              "\n* missing outline: cannot create default" ) );
    }

    if( aBoard->GetUnit() == IDF3::UNIT_THOU )
    {
        xoff *= IDF_THOU_TO_MM;
        yoff *= IDF_THOU_TO_MM;
        zoff *= IDF_THOU_TO_MM;
    }

    parent = aBoard->FindComponent( refdes );

    if( parent == nullptr )
    {
        IDF3_COMPONENT* cp = new IDF3_COMPONENT( aBoard );

        if( cp == nullptr )
        {
            outline = nullptr;

            throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
                              "cannot create component object" ) );
        }

        cp->SetRefDes( refdes );
        cp->SetPosition( xoff, yoff, aoff, side );
        cp->SetPlacement( placement );

        xoff = 0;
        yoff = 0;
        aoff = 0;

        aBoard->AddComponent( cp );

        parent = cp;
    }
    else
    {
        double tX, tY, tA;
        IDF3::IDF_LAYER tL;

        if( parent->GetPosition( tX, tY, tA, tL ) )
        {
            if( side != tL )
            {
                outline = nullptr;
                ostringstream ostr;

                ostr << "invalid IDF file\n";
                ostr << "* violation: inconsistent PLACEMENT data; ";
                ostr << "* SIDE value has changed from " << GetLayerString( tL );
                ostr << " to " << GetLayerString( side ) << "\n";
                ostr << "* line: '" << iline << "'\n";
                ostr << "* file position: " << pos;

                throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) );
            }

            xoff -= tX;
            yoff -= tY;
            aoff -= tA;
        }
        else
        {
            parent->SetPosition( xoff, yoff, aoff, side );
            parent->SetPlacement( placement );

            xoff = 0;
            yoff = 0;
            aoff = 0;
        }

        if( placement != parent->GetPlacement() )
        {
            outline = nullptr;
            ostringstream ostr;

            ostr << "invalid IDF file\n";
            ostr << "* violation: inconsistent PLACEMENT data; ";
            ostr << "* PLACEMENT value has changed from ";
            ostr << GetPlacementString( parent->GetPlacement() );
            ostr << " to " << GetPlacementString( placement ) << "\n";
            ostr << "* line: '" << iline << "'\n";
            ostr << "* file position: " << pos;

            throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) );
        }

    }

    // copy internal data to a new object and push it into the component's outline list
    IDF3_COMP_OUTLINE_DATA* cdp = new IDF3_COMP_OUTLINE_DATA;
    *cdp = *this;
    if( outline ) outline->incrementRef();
    outline = nullptr;

    if( !parent->AddOutlineData( cdp ) )
    {
        delete cdp;

        throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
                          "could not add outline data object" ) );
    }

    return true;
}


void IDF3_COMP_OUTLINE_DATA::writePlaceData( std::ostream& aBoardFile,
                                             double aXpos, double aYpos, double aAngle,
                                             const std::string& aRefDes,
                                             IDF3::IDF_PLACEMENT aPlacement,
                                             IDF3::IDF_LAYER aSide )
{
    if( outline == nullptr )
        return;

    if( outline->GetUID().empty() )
        throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
                          "empty GEOM and PART names" ) );

    if( aPlacement == PS_INVALID )
    {
        ERROR_IDF << "placement invalid (" << aRefDes << ":";
        aPlacement = PS_PLACED;
    }

    if( aSide != LYR_TOP && aSide != LYR_BOTTOM )
    {
        ostringstream ostr;
        ostr << "\n* invalid side (" << GetLayerString( aSide ) << "); ";
        ostr << "must be TOP or BOTTOM\n";

        throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) );
    }

    // calculate the final position based on layer
    double xpos, ypos, ang;

    switch( aSide )
    {
        case LYR_TOP:
            xpos = aXpos + xoff;
            ypos = aYpos + yoff;
            ang  = aAngle + aoff;
            break;

        default:
            xpos = aXpos - xoff;
            ypos = aYpos + yoff;
            ang  = aAngle - aoff;
            break;
    }

    std::string arefdes = aRefDes;

    if( arefdes.empty() || !arefdes.compare( "~" )
        || ( arefdes.size() >= 8 && CompareToken( "NOREFDES", arefdes.substr(0, 8) ) ) )
        arefdes = "NOREFDES";

    aBoardFile << "\"" << outline->GetGeomName() << "\" \"" << outline->GetPartName() << "\" "
    << arefdes << "\n";

    IDF3::IDF_UNIT unit = UNIT_MM;

    if( parent )
        unit = parent->GetUnit();

    if( unit == UNIT_MM )
    {
        aBoardFile << setiosflags(ios::fixed) << setprecision(5) << xpos << " "
        << ypos << " " << setprecision(3) << zoff << " "
        << ang << " ";
    }
    else
    {
        aBoardFile << setiosflags(ios::fixed) << setprecision(1) << (xpos / IDF_THOU_TO_MM) << " "
        << (ypos / IDF_THOU_TO_MM) << " "  << (zoff / IDF_THOU_TO_MM) << " "
        << setprecision(3) << ang << " ";
    }

    WriteLayersText( aBoardFile, aSide );

    switch( aPlacement )
    {
        case PS_PLACED:
            aBoardFile << " PLACED\n";
            break;

        case PS_UNPLACED:
            aBoardFile << " UNPLACED\n";
            break;

        case PS_MCAD:
            aBoardFile << " MCAD\n";
            break;

        default:
            aBoardFile << " ECAD\n";
            break;
    }
}


IDF3_COMPONENT::IDF3_COMPONENT( IDF3_BOARD* aParent )
{
    xpos   = 0.0;
    ypos   = 0.0;
    angle  = 0.0;

    hasPosition = false;
    placement   = PS_INVALID;
    layer       = LYR_INVALID;

    parent = aParent;
}


IDF3_COMPONENT::~IDF3_COMPONENT()
{
    std::list< IDF3_COMP_OUTLINE_DATA* >::iterator itcS = components.begin();
    std::list< IDF3_COMP_OUTLINE_DATA* >::iterator itcE = components.end();

    while( itcS != itcE )
    {
        delete *itcS;
        ++itcS;
    }

    components.clear();

    std::list< IDF_DRILL_DATA* >::iterator itdS = drills.begin();
    std::list< IDF_DRILL_DATA* >::iterator itdE = drills.end();

    while( itdS != itdE )
    {
        delete *itdS;
        ++itdS;
    }

    drills.clear();
}


#ifndef DISABLE_IDF_OWNERSHIP
bool IDF3_COMPONENT::checkOwnership( int aSourceLine, const char* aSourceFunc )
{
    if( !parent )
    {
        ostringstream ostr;
        ostr << __FILE__ << ":" << aSourceLine << ":" << aSourceFunc << "():\n";
        ostr << "\n* BUG: parent not set";
        errormsg = ostr.str();

        return false;
    }

    IDF3::CAD_TYPE pcad = parent->GetCadType();

    switch( placement )
    {
        case PS_UNPLACED:
        case PS_PLACED:
        case PS_INVALID:
            break;

        case PS_MCAD:

            if( pcad != CAD_MECH )
            {
                ostringstream ostr;
                ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "():\n";
                ostr << "\n* ownership violation; internal CAD type (MCAD) conflicts with "
                        "PLACEMENT (";
                ostr << GetPlacementString( placement ) << ")";
                errormsg = ostr.str();

                return false;
            }

            break;

        case PS_ECAD:

            if( pcad != CAD_ELEC )
            {
                ostringstream ostr;
                ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "():\n";
                ostr << "\n* ownership violation; internal CAD type (MCAD) conflicts with "
                        "PLACEMENT (";
                ostr << GetPlacementString( placement ) << ")";
                errormsg = ostr.str();

                return false;
            }
            break;

        default:
            do
            {
                ostringstream ostr;
                ostr << "\n* BUG: unhandled internal placement value (" << placement << ")";
                errormsg = ostr.str();

                return false;
            } while( 0 );

            break;
    }

    return true;
}
#endif


void IDF3_COMPONENT::SetParent( IDF3_BOARD* aParent )
{
    parent = aParent;
    return;
}

IDF3::CAD_TYPE IDF3_COMPONENT::GetCadType( void )
{
    if( parent )
        return parent->GetCadType();

    return CAD_INVALID;
}

IDF3::IDF_UNIT IDF3_COMPONENT::GetUnit( void )
{
    if( parent )
        return parent->GetUnit();

    return UNIT_INVALID;
}

bool IDF3_COMPONENT::SetRefDes( const std::string& aRefDes )
{
#ifndef DISABLE_IDF_OWNERSHIP
    if( !checkOwnership( __LINE__, __FUNCTION__ ) )
        return false;
#endif

    if( aRefDes.empty() )
    {
        ostringstream ostr;
        ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "(): invalid RefDes (empty)";
        errormsg = ostr.str();

        return false;
    }

    if( CompareToken( "PANEL", aRefDes ) )
    {
        ostringstream ostr;
        ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "():\n";
        ostr <<  "* BUG: PANEL is a reserved designator and may not be used by components";
        errormsg = ostr.str();

        return false;
    }

    refdes = aRefDes;
    return true;
}


const std::string& IDF3_COMPONENT::GetRefDes( void )
{
    return refdes;
}


IDF_DRILL_DATA* IDF3_COMPONENT::AddDrill( double aDia, double aXpos, double aYpos,
                                          IDF3::KEY_PLATING aPlating,
                                          const std::string& aHoleType,
                                          IDF3::KEY_OWNER aOwner )
{
    IDF_DRILL_DATA* dp = new IDF_DRILL_DATA( aDia, aXpos, aYpos, aPlating,
                                             refdes, aHoleType, aOwner );

    if( dp == nullptr )
        return nullptr;

    drills.push_back( dp );

    return dp;
}


IDF_DRILL_DATA* IDF3_COMPONENT::AddDrill( IDF_DRILL_DATA* aDrilledHole )
{
    if( !aDrilledHole )
        return nullptr;

    if( CompareToken( "PANEL", refdes ) )
    {
        ERROR_IDF;
        cerr << "\n* BUG: PANEL drills not supported at component level\n";
        return nullptr;
    }

    if( refdes.compare( aDrilledHole->GetDrillRefDes() ) )
    {
        ERROR_IDF;
        cerr << "\n* BUG: pushing an incorrect REFDES ('" << aDrilledHole->GetDrillRefDes();
        cerr << "') to component ('" << refdes << "')\n";
        return nullptr;
    }

    drills.push_back( aDrilledHole );

    return aDrilledHole;
}


bool IDF3_COMPONENT::DelDrill( double aDia, double aXpos, double aYpos )
{
#ifndef DISABLE_IDF_OWNERSHIP
    if( !checkOwnership( __LINE__, __FUNCTION__ ) )
        return false;
#endif

    errormsg.clear();

    if( drills.empty() )
        return false;

    bool val = false;

    list< IDF_DRILL_DATA* >::iterator itS = drills.begin();
    list< IDF_DRILL_DATA* >::iterator itE = drills.end();

    while( !drills.empty() && itS != itE )
    {
        if( (*itS)->Matches( aDia, aXpos, aYpos ) )
        {
            val = true;
            delete *itS;
            itS = drills.erase( itS );
            continue;
        }

        ++itS;
    }

    return val;
}


bool IDF3_COMPONENT::DelDrill( IDF_DRILL_DATA* aDrill )
{
#ifndef DISABLE_IDF_OWNERSHIP
    if( !checkOwnership( __LINE__, __FUNCTION__ ) )
        return false;
#endif

    errormsg.clear();

    if( drills.empty() )
        return false;

    list< IDF_DRILL_DATA* >::iterator itS = drills.begin();
    list< IDF_DRILL_DATA* >::iterator itE = drills.end();

    while( !drills.empty() && itS != itE )
    {
        if( *itS == aDrill )
        {
            delete *itS;
            drills.erase( itS );
            return true;
        }

        ++itS;
    }

    return false;
}


const std::list< IDF_DRILL_DATA* >*const IDF3_COMPONENT::GetDrills( void )
{
    return &drills;
}

bool IDF3_COMPONENT::AddOutlineData( IDF3_COMP_OUTLINE_DATA* aComponentOutline )
{
    if( aComponentOutline == nullptr )
    {
        ostringstream ostr;
        ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ <<
                "(): invalid aComponentOutline (nullptr)";
        errormsg = ostr.str();

        return false;
    }


    components.push_back( aComponentOutline );

    return true;
}


bool IDF3_COMPONENT::DeleteOutlineData( IDF3_COMP_OUTLINE_DATA* aComponentOutline )
{
#ifndef DISABLE_IDF_OWNERSHIP
    if( !checkOwnership( __LINE__, __FUNCTION__ ) )
        return false;
#endif

    if( components.empty() )
    {
        ostringstream ostr;
        ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "(): component list is empty";
        errormsg = ostr.str();

        return false;
    }

    if( aComponentOutline == nullptr )
    {
        ostringstream ostr;
        ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ <<
                "(): invalid aComponentOutline (nullptr)";
        errormsg = ostr.str();

        return false;
    }

    errormsg.clear();

    std::list< IDF3_COMP_OUTLINE_DATA* >::iterator itS = components.begin();
    std::list< IDF3_COMP_OUTLINE_DATA* >::iterator itE = components.end();

    while( itS != itE )
    {
        if( *itS == aComponentOutline )
        {
            delete *itS;
            components.erase( itS );
            return true;
        }

        ++itS;
    }

    return false;
}


bool IDF3_COMPONENT::DeleteOutlineData( size_t aIndex )
{
#ifndef DISABLE_IDF_OWNERSHIP
    if( !checkOwnership( __LINE__, __FUNCTION__ ) )
        return false;
#endif

    if( aIndex >= components.size() )
    {
        ostringstream ostr;
        ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "():\n";
        ostr << "* aIndex (" << aIndex << ") out of range; list size is " << components.size();
        errormsg = ostr.str();

        return false;
    }

    std::list< IDF3_COMP_OUTLINE_DATA* >::iterator itS = components.begin();
    std::list< IDF3_COMP_OUTLINE_DATA* >::iterator itE = components.end();
    size_t idx = 0;

    while( itS != itE )
    {
        if( idx == aIndex )
        {
            delete *itS;
            components.erase( itS );
            return true;
        }

        ++idx;
        ++itS;
    }

    return false;
}


size_t IDF3_COMPONENT::GetOutlinesSize( void )
{
    return components.size();
}


const std::list< IDF3_COMP_OUTLINE_DATA* >*const IDF3_COMPONENT::GetOutlinesData( void )
{
    return &components;
}


bool IDF3_COMPONENT::GetPosition( double& aXpos, double& aYpos, double& aAngle,
                                  IDF3::IDF_LAYER& aLayer )
{
    errormsg.clear();

    if( !hasPosition )
    {
        aXpos = 0.0;
        aYpos = 0.0;
        aAngle = 0.0;
        aLayer = IDF3::LYR_INVALID;
        return false;
    }

    aXpos = xpos;
    aYpos = ypos;
    aAngle = angle;
    aLayer = layer;
    return true;
}


bool IDF3_COMPONENT::SetPosition( double aXpos, double aYpos, double aAngle,
                                  IDF3::IDF_LAYER aLayer )
{
#ifndef DISABLE_IDF_OWNERSHIP
    if( !checkOwnership( __LINE__, __FUNCTION__ ) )
        return false;
#endif

    errormsg.clear();

    switch( aLayer )
    {
        case LYR_TOP:
        case LYR_BOTTOM:
            break;

        default:
            do
            {
                ostringstream ostr;
                ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "():\n";
                ostr << "\n* invalid side (must be TOP or BOTTOM only): " <<
                        GetLayerString( aLayer );
                errormsg = ostr.str();

                return false;
            } while( 0 );

            break;
    }

    if( hasPosition )
        return false;

    hasPosition = true;
    xpos = aXpos;
    ypos = aYpos;
    angle = aAngle;
    layer = aLayer;
    return true;
}


IDF3::IDF_PLACEMENT IDF3_COMPONENT::GetPlacement( void )
{
    return placement;
}


bool IDF3_COMPONENT::SetPlacement( IDF3::IDF_PLACEMENT aPlacementValue )
{
    if( aPlacementValue < PS_UNPLACED || aPlacementValue >= PS_INVALID )
    {
        ostringstream ostr;
        ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "():\n";
        ostr << "\n* invalid PLACEMENT value (" << aPlacementValue << ")";
        errormsg = ostr.str();

        return false;
    }

#ifndef DISABLE_IDF_OWNERSHIP
    if( !checkOwnership( __LINE__, __FUNCTION__ ) )
        return false;
#endif

    placement = aPlacementValue;

    return true;
}


bool IDF3_COMPONENT::writeDrillData( std::ostream& aBoardFile )
{
    if( drills.empty() )
        return true;

    std::list< IDF_DRILL_DATA* >::iterator itS = drills.begin();
    std::list< IDF_DRILL_DATA* >::iterator itE = drills.end();

    while( itS != itE )
    {
        (*itS)->write( aBoardFile, GetUnit() );
        ++itS;
    }

    return true;
}


bool IDF3_COMPONENT::writePlaceData( std::ostream& aBoardFile )
{
    if( components.empty() )
        return true;

    std::list< IDF3_COMP_OUTLINE_DATA* >::iterator itS = components.begin();
    std::list< IDF3_COMP_OUTLINE_DATA* >::iterator itE = components.end();

    while( itS != itE )
    {
        (*itS)->writePlaceData( aBoardFile, xpos, ypos, angle, refdes, placement, layer );
        ++itS;
    }

    return true;
}


IDF3_BOARD::IDF3_BOARD( IDF3::CAD_TYPE aCadType )
{
    idfVer         = IDF_V3;
    cadType        = aCadType;
    userPrec       = 5;
    userScale      = 1.0;
    userXoff       = 0.0;
    userYoff       = 0.0;
    brdFileVersion = 0;
    libFileVersion = 0;
    iRefDes        = 0;
    unit           = UNIT_MM;

    // unlike other outlines which are created as necessary,
    // the board outline always exists and its parent must
    // be set here
    olnBoard.setParent( this );
    olnBoard.setThickness( 1.6 );

    return;
}


IDF3_BOARD::~IDF3_BOARD()
{
    Clear();

    return;
}


const std::string& IDF3_BOARD::GetNewRefDes( void )
{
    ostringstream ostr;
    ostr << "NOREFDESn" << iRefDes++;

    sRefDes = ostr.str();

    return sRefDes;
}


#ifndef DISABLE_IDF_OWNERSHIP
bool IDF3_BOARD::checkComponentOwnership( int aSourceLine, const char* aSourceFunc,
                                          IDF3_COMPONENT* aComponent )
{
    if( !aComponent )
    {
        ostringstream ostr;
        ostr << __FILE__ << ":" << aSourceLine << ":" << aSourceFunc;
        ostr << "(): Invalid component pointer (nullptr)";
        errormsg = ostr.str();

        return false;
    }

    IDF3::IDF_PLACEMENT place = aComponent->GetPlacement();

    if( place == PS_PLACED || place == PS_UNPLACED )
        return true;

    if( place == PS_MCAD && cadType == CAD_MECH )
        return true;

    if( place == PS_ECAD && cadType == CAD_ELEC )
        return true;

    do
    {
        ostringstream ostr;
        ostr << "* " << __FILE__ << ":" << aSourceLine << ":" << aSourceFunc << "():\n";
        ostr << "* ownership violation; CAD type is ";

        if( cadType == CAD_MECH )
            ostr << "MCAD ";
        else
            ostr << "ECAD ";

        ostr << "while outline owner is " << GetPlacementString( place ) << "\n";
        errormsg = ostr.str();

    } while( 0 );

    return false;
}
#endif


IDF3::CAD_TYPE IDF3_BOARD::GetCadType( void )
{
    return cadType;
}


void IDF3_BOARD::SetBoardName( const std::string& aBoardName )
{
    boardName = std::move( aBoardName );
}


const std::string& IDF3_BOARD::GetBoardName( void )
{
    return boardName;
}


bool IDF3_BOARD::setUnit( IDF3::IDF_UNIT aUnit, bool convert )
{
    switch( aUnit )
    {
    case UNIT_MM:
    case UNIT_THOU:
        unit = aUnit;
        break;

    case UNIT_TNM:
        ERROR_IDF << "\n* TNM unit is not supported; defaulting to mm\n";
        unit = UNIT_MM;
        break;

    default:
        do
        {
            ostringstream ostr;
            ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "():\n";
            ostr << "* invalid board unit (" << aUnit << ")";
            errormsg = ostr.str();

            return false;
        } while( 0 );

        break;
    }

    // iterate through all owned OUTLINE objects (except IDF3_COMP_OUTLINE)
    // and set to the same unit

    olnBoard.SetUnit( aUnit );

    do
    {
        std::map< std::string, OTHER_OUTLINE*>::iterator its = olnOther.begin();
        std::map< std::string, OTHER_OUTLINE*>::iterator ite = olnOther.end();

        while( its != ite )
        {
            its->second->SetUnit( aUnit );
            ++its;
        }

    } while( 0 );

    do
    {
        std::list<ROUTE_OUTLINE*>::iterator its = olnRoute.begin();
        std::list<ROUTE_OUTLINE*>::iterator ite = olnRoute.end();

        while( its != ite )
        {
            (*its)->SetUnit( aUnit );
            ++its;
        }

    } while( 0 );

    do
    {
        std::list<PLACE_OUTLINE*>::iterator its = olnPlace.begin();
        std::list<PLACE_OUTLINE*>::iterator ite = olnPlace.end();

        while( its != ite )
        {
            (*its)->SetUnit( aUnit );
            ++its;
        }

    } while( 0 );

    do
    {
        std::list<ROUTE_KO_OUTLINE*>::iterator its = olnRouteKeepout.begin();
        std::list<ROUTE_KO_OUTLINE*>::iterator ite = olnRouteKeepout.end();

        while( its != ite )
        {
            (*its)->SetUnit( aUnit );
            ++its;
        }

    } while( 0 );

    do
    {
        std::list<VIA_KO_OUTLINE*>::iterator its = olnViaKeepout.begin();
        std::list<VIA_KO_OUTLINE*>::iterator ite = olnViaKeepout.end();

        while( its != ite )
        {
            (*its)->SetUnit( aUnit );
            ++its;
        }

    } while( 0 );

    do
    {
        std::list<PLACE_KO_OUTLINE*>::iterator its = olnPlaceKeepout.begin();
        std::list<PLACE_KO_OUTLINE*>::iterator ite = olnPlaceKeepout.end();

        while( its != ite )
        {
            (*its)->SetUnit( aUnit );
            ++its;
        }

    } while( 0 );

    do
    {
        std::multimap<std::string, GROUP_OUTLINE*>::iterator its = olnGroup.begin();
        std::multimap<std::string, GROUP_OUTLINE*>::iterator ite = olnGroup.end();

        while( its != ite )
        {
            its->second->SetUnit( aUnit );
            ++its;
        }

    } while( 0 );

    //iterate through all owned IDF3_COMP_OUTLINE objects and
    // set to the same unit IF convert = true
    if( convert )
    {
        std::map<std::string, IDF3_COMP_OUTLINE*>::iterator its = compOutlines.begin();
        std::map<std::string, IDF3_COMP_OUTLINE*>::iterator ite = compOutlines.end();

        while( its != ite )
        {
            its->second->SetUnit( aUnit );
            ++its;
        }

    }

    return true;
}


IDF3::IDF_UNIT IDF3_BOARD::GetUnit( void )
{
    return unit;
}


bool IDF3_BOARD::SetBoardThickness( double aBoardThickness )
{
    if( aBoardThickness <= 0.0 )
    {
        ostringstream ostr;
        ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "(): ";
        ostr << "board thickness (" << aBoardThickness << ") must be > 0";
        errormsg = ostr.str();

        return false;
    }

    if( !olnBoard.SetThickness( aBoardThickness ) )
    {
        errormsg = olnBoard.GetError();
        return false;
    }

    return true;
}


double IDF3_BOARD::GetBoardThickness( void )
{
    return olnBoard.GetThickness();
}


void IDF3_BOARD::readBrdDrills( std::istream& aBoardFile, IDF3::FILE_STATE& aBoardState )
{
    IDF_DRILL_DATA drill;

    while( drill.read( aBoardFile, unit, aBoardState, idfVer ) )
    {
        IDF_DRILL_DATA *dp = new IDF_DRILL_DATA;
        *dp = drill;

        if( AddDrill( dp ) == nullptr )
        {
            delete dp;

            throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
                              "\n* BUG: could not add drill data; cannot continue reading the "
                              "file" ) );
        }
    }
}


void IDF3_BOARD::readBrdNotes( std::istream& aBoardFile, IDF3::FILE_STATE& aBoardState )
{
    IDF_NOTE note;

    while( note.readNote( aBoardFile, aBoardState, unit ) )
    {
        IDF_NOTE *np = new IDF_NOTE;
        *np = note;
        notes.push_back( np );
    }
}


void IDF3_BOARD::readBrdPlacement( std::istream& aBoardFile, IDF3::FILE_STATE& aBoardState,
                                   bool aNoSubstituteOutlines )
{
    IDF3_COMP_OUTLINE_DATA oldata;

    while( oldata.readPlaceData( aBoardFile, aBoardState, this, idfVer, aNoSubstituteOutlines ) );
}


void IDF3_BOARD::readBrdHeader( std::istream& aBoardFile, IDF3::FILE_STATE& aBoardState )
{
    std::string iline;      // the input line
    bool isComment;         // true if a line just read in is a comment line
    std::streampos pos;
    int idx = 0;
    bool quoted = false;
    std::string token;

    // RECORD 1: ".HEADER" must be the very first line
    while( !FetchIDFLine( aBoardFile, iline, isComment, pos ) && aBoardFile.good() );

    if( !aBoardFile.good() )
        throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
                          "problems reading board header" ) );

    if( isComment )
        throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
                          "invalid IDF file\n"
                          "* Violation of specification: first line must be .HEADER\n" ) );

    if( !CompareToken( ".HEADER", iline ) )
        throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
                          "invalid IDF file\n"
                          "* Violation of specification:\n"
                          "* first line must be .HEADER and have no quotes or trailing text" ) );

    // RECORD 2:
    //      File Type [str]: BOARD_FILE (PANEL_FILE not supported)
    //      IDF Version Number [float]: must be 3.0
    //      Source System [str]: ignored
    //      Date [str]: ignored
    //      Board File Version [int]: ignored
    while( !FetchIDFLine( aBoardFile, iline, isComment, pos ) && aBoardFile.good() );

    if( !aBoardFile.good() )
        throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
                          "problems reading board header, RECORD 2" ) );

    if( isComment )
        throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
                          "invalid IDF file\n"
                          "* Violation of specification: comment within .HEADER section" ) );

    idx = 0;
    GetIDFString( iline, token, quoted, idx );

    if( quoted )
        throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
                          "invalid IDF file\n"
                          "* Violation of specification:\n"
                          "* File Type in HEADER section must not be in quotes" ) );

    if( !CompareToken( "BOARD_FILE", token ) )
    {
        ERROR_IDF;

        if( CompareToken( "PANEL_FILE", token ) )
            throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
                              "not a board file\n"
                              "* PANEL_FILE is not supported (expecting BOARD_FILE)" ) );
        else
            throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
                              "invalid IDF file\n"
                              "* Expecting string: BOARD_FILE" ) );
    }

    if( !GetIDFString( iline, token, quoted, idx ) )
        throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
                          "invalid IDF file\n"
                          "* Violation of specification: HEADER section, RECORD 2: no FIELD 2" ) );

    if( quoted )
        throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
                          "invalid IDF file\n"
                          "* Violation of specification: IDF Version must not be in quotes" ) );

    if( !token.compare( "3.0" ) || !token.compare( "3." ) || !token.compare( "3" ) )
    {
        idfVer = IDF_V3;
    }
    else if( !token.compare( "2.0" ) || !token.compare( "2." ) || !token.compare( "2" ) )
    {
        idfVer = IDF_V2;
    }
    else
    {
        ostringstream ostr;

        ostr << "unsupported IDF version\n";
        ostr << "* Expecting version to be a variant of '3.0', '2.0' (value: '" << token << "')\n";

        throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) );
    }

    if( !GetIDFString( iline, token, quoted, idx ) )
        throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
                          "invalid IDF file\n"
                          "* Violation of specification:\n"
                          "* HEADER section, RECORD 2, FIELD 3: no Source System string" ) );

    brdSource = token;

    if( !GetIDFString( iline, token, quoted, idx ) )
        throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
                          "invalid IDF file\n"
                          "* Violation of specification:\n"
                          "* HEADER section, RECORD 2, FIELD 4: no Date string" ) );

    brdDate = token;

    if( !GetIDFString( iline, token, quoted, idx ) )
        throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
                          "invalid IDF file\n"
                          "* Violation of specification:\n"
                          "* HEADER section, RECORD 2, FIELD 5: no Board File Version number" ) );

    std::istringstream istr;
    istr.str( token );

    istr >> brdFileVersion;

    if( istr.fail() )
    {
        ERROR_IDF << "invalid Board File Version in header\n";
        cerr << "* Setting default version of 1\n";
        brdFileVersion = 1;
    }

    if( quoted )
        throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
                          "invalid IDF file\n"
                          "* Violation of specification:\n"
                          "* HEADER section, RECORD 2, FIELD 5: Board File Version must not be "
                          "in quotes" ) );

    // RECORD 3:
    //      Board Name [str]: stored
    //      Units [str]: MM or THOU
    while( !FetchIDFLine( aBoardFile, iline, isComment, pos ) && aBoardFile.good() );

    if( !aBoardFile.good() )
        throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
                          "\n* problems reading board header, RECORD 2" ) );

    if( isComment )
        throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
                          "invalid IDF file\n"
                          "* Violation of specification: comment within .HEADER section" ) );

    idx = 0;
    GetIDFString( iline, token, quoted, idx );

    boardName = token;

    if( !GetIDFString( iline, token, quoted, idx ) )
        throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
                          "invalid IDF file\n"
                          "* Violation of specification:\n"
                          "* HEADER section, RECORD 3, FIELD 1: no Board Name" ) );

    if( quoted )
        throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
                          "invalid IDF file\n"
                          "* Violation of specification:\n"
                          "* HEADER section, RECORD 3, FIELD 2: UNIT may not be in quotes" ) );

    if( CompareToken( "MM", token ) )
    {
        unit = IDF3::UNIT_MM;
    }
    else if( CompareToken( "THOU", token ) )
    {
        unit = IDF3::UNIT_THOU;
    }
    else if( ( idfVer == IDF_V2 ) && CompareToken( "TNM", token ) )
    {
        unit = IDF3::UNIT_TNM;
    }
    else
    {
        ostringstream ostr;

        ostr << "invalid IDF file\n";
        ostr << "* HEADER section, RECORD 3, FIELD 2: expecting MM or THOU (got '" << token <<
                "')\n";

        throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) );
    }

    olnBoard.SetUnit( unit );

    // RECORD 4:
    //      .END_HEADER
    while( !FetchIDFLine( aBoardFile, iline, isComment, pos ) && aBoardFile.good() );

    if( ( !aBoardFile.good() && !aBoardFile.eof() ) || iline.empty() )
        throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
                          "problems reading board header, RECORD 4" ) );

    if( isComment )
        throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
                          "invalid IDF file\n"
                          "* Violation of specification: comment within .HEADER section\n" ) );

    if( !CompareToken( ".END_HEADER", iline ) )
    {
        ostringstream ostr;

        ostr << "invalid IDF file\n";
        ostr << "* Violation of specification: expected .END_HEADER\n";
        ostr << "* line: '" << iline << "'";

        throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) );
    }

    aBoardState = IDF3::FILE_HEADER;
}


void IDF3_BOARD::readBrdSection( std::istream& aBoardFile, IDF3::FILE_STATE& aBoardState,
                                 bool aNoSubstituteOutlines )
{
    std::list< std::string > comments;  // comments associated with a section

    // Reads in .SECTION_ID or #COMMENTS
    // Expected SECTION IDs:
    //      .BOARD_OUTLINE
    //      .PANEL_OUTLINE (NOT SUPPORTED)
    //      .OTHER_OUTLINE
    //      .ROUTE_OUTLINE
    //      .PLACE_OUTLINE
    //      .ROUTE_KEEPOUT
    //      .VIA_KEEPOUT
    //      .PLACE_KEEPOUT
    //      .PLACE_REGION
    //      .DRILLED_HOLES
    //      .NOTES
    //      .PLACEMENT
    std::string iline;      // the input line
    bool isComment;         // true if a line just read in is a comment line
    std::streampos pos;
    int idx = 0;
    bool quoted = false;
    std::string token;

    while( aBoardFile.good() )
    {
        while( !FetchIDFLine( aBoardFile, iline, isComment, pos ) && aBoardFile.good() );

        if( !aBoardFile.good() )
        {
            if( aBoardFile.eof() && aBoardState >= IDF3::FILE_HEADER &&
                aBoardState < IDF3::FILE_INVALID )
            {
                if( !comments.empty() )
                    ERROR_IDF << "[warning]: trailing comments in IDF file (comments will be "
                                 "lost)\n";

                return;
            }

            throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
                              "problems reading board section" ) );
        }

        if( isComment )
        {
            comments.push_back( iline );
            continue;
        }

        // This must be a header
        GetIDFString( iline, token, quoted, idx );

        if( quoted )
        {
            ostringstream ostr;

            ostr << "invalid IDF file\n";
            ostr << "* Violation of specification: quoted string where SECTION HEADER expected\n";
            ostr << "* line: '" << iline << "'";
            ostr << "* position: " << pos;

            throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) );
        }

        if( CompareToken( ".BOARD_OUTLINE", token ) )
        {
            if( aBoardState != IDF3::FILE_HEADER )
            {
                aBoardState = IDF3::FILE_INVALID;
                throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
                                  "invalid IDF file\n"
                                  "* Violation of specification: no HEADER section" ) );
            }

            olnBoard.readData( aBoardFile, iline, idfVer );

            if( !comments.empty() )
            {
                std::list<std::string>::iterator its = comments.begin();
                std::list<std::string>::iterator ite = comments.end();

                while( its != ite )
                {
                    olnBoard.AddComment( *its );
                    ++its;
                }
            }

            aBoardState = IDF3::FILE_OUTLINE;
            return;
        }

        if( CompareToken( ".PANEL_OUTLINE", token ) )
            throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
                              "PANEL_OUTLINE not supported" ) );

        if( CompareToken( ".OTHER_OUTLINE", token ) )
        {
            if( aBoardState != IDF3::FILE_OUTLINE )
                throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
                                  "invalid IDF file\n"
                                  "* Violation of specification: expecting .BOARD_OUTLINE, have "
                                  ".OTHER_OUTLINE" ) );

            OTHER_OUTLINE* op = new OTHER_OUTLINE( this );

            if( op == nullptr )
                throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
                                  "could not create OTHER_OUTLINE object" ) );

            op->SetUnit( unit );
            op->readData( aBoardFile, iline, idfVer );

            if( !comments.empty() )
            {
                std::list<std::string>::iterator its = comments.begin();
                std::list<std::string>::iterator ite = comments.end();

                while( its != ite )
                {
                    op->AddComment( *its );
                    ++its;
                }
            }

            if( olnOther.insert( pair<string,
                                 OTHER_OUTLINE*>(op->GetOutlineIdentifier(), op ) ).second == false )
            {
                ostringstream ostr;
                ostr << "invalid IDF file\n";
                ostr << "* Violation of specification. Non-unique ID in OTHER_OUTLINE '";
                ostr << op->GetOutlineIdentifier() << "'\n";
                ostr << "* line: '" << iline << "'\n";
                ostr << "* pos: " << pos;
                delete op;

                throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) );
            }

            return;
        }

        if( CompareToken( ".ROUTE_OUTLINE", token ) )
        {
            if( aBoardState != IDF3::FILE_OUTLINE )
                throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
                                  "invalid IDF file\n"
                                  "* Violation of specification: expecting .BOARD_OUTLINE, have "
                                  ".ROUTE_OUTLINE" ) );

            ROUTE_OUTLINE* op = new ROUTE_OUTLINE( this );

            if( op == nullptr )
                throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
                                  "could not create ROUTE_OUTLINE object" ) );

            op->SetUnit( unit );
            op->readData( aBoardFile, iline, idfVer );

            if( !comments.empty() )
            {
                std::list<std::string>::iterator its = comments.begin();
                std::list<std::string>::iterator ite = comments.end();

                while( its != ite )
                {
                    op->AddComment( *its );
                    ++its;
                }
            }

            olnRoute.push_back( op );

            return;
        }

        if( CompareToken( ".PLACE_OUTLINE", token ) )
        {
            if( aBoardState != IDF3::FILE_OUTLINE )
                throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
                                  "invalid IDF file\n"
                                  "* Violation of specification: expecting .BOARD_OUTLINE, have "
                                  ".PLACE_OUTLINE" ) );

            PLACE_OUTLINE* op = new PLACE_OUTLINE( this );

            if( op == nullptr )
                throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
                                  "could not create PLACE_OUTLINE object" ) );

            op->SetUnit( unit );
            op->readData( aBoardFile, iline, idfVer );

            if( !comments.empty() )
            {
                std::list<std::string>::iterator its = comments.begin();
                std::list<std::string>::iterator ite = comments.end();

                while( its != ite )
                {
                    op->AddComment( *its );
                    ++its;
                }
            }

            olnPlace.push_back( op );

            return;
        }

        if( CompareToken( ".ROUTE_KEEPOUT", token ) )
        {
            if( aBoardState != IDF3::FILE_OUTLINE )
                throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
                                  "invalid IDF file\n"
                                  "* Violation of specification: expecting .BOARD_OUTLINE, have "
                                  ".ROUTE_KEEPOUT" ) );

            ROUTE_KO_OUTLINE* op = new ROUTE_KO_OUTLINE( this );

            if( op == nullptr )
                throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
                                  "could not create ROUTE_KEEPOUT object" ) );

            op->SetUnit( unit );
            op->readData( aBoardFile, iline, idfVer );

            if( !comments.empty() )
            {
                std::list<std::string>::iterator its = comments.begin();
                std::list<std::string>::iterator ite = comments.end();

                while( its != ite )
                {
                    op->AddComment( *its );
                    ++its;
                }
            }

            olnRouteKeepout.push_back( op );

            return;
        }

        if( CompareToken( ".VIA_KEEPOUT", token ) )
        {
            if( aBoardState != IDF3::FILE_OUTLINE )
                throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
                                  "invalid IDF file\n"
                                  "* Violation of specification: expecting .BOARD_OUTLINE, have "
                                  ".VIA_KEEPOUT" ) );

            VIA_KO_OUTLINE* op = new VIA_KO_OUTLINE( this );

            if( op == nullptr )
                throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
                                  "could not create VIA_KEEPOUT object" ) );

            op->SetUnit( unit );
            op->readData( aBoardFile, iline, idfVer );

            if( !comments.empty() )
            {
                std::list<std::string>::iterator its = comments.begin();
                std::list<std::string>::iterator ite = comments.end();

                while( its != ite )
                {
                    op->AddComment( *its );
                    ++its;
                }
            }

            olnViaKeepout.push_back( op );

            return;
        }

        if( CompareToken( ".PLACE_KEEPOUT", token ) )
        {
            if( aBoardState != IDF3::FILE_OUTLINE )
                throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
                                  "invalid IDF file\n"
                                  "* Violation of specification: expecting .BOARD_OUTLINE, have "
                                  ".PLACE_KEEPOUT" ) );

            PLACE_KO_OUTLINE* op = new PLACE_KO_OUTLINE( this );

            if( op == nullptr )
                throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
                                  "could not create PLACE_KEEPOUT object" ) );

            op->SetUnit( unit );
            op->readData( aBoardFile, iline, idfVer );

            if( !comments.empty() )
            {
                std::list<std::string>::iterator its = comments.begin();
                std::list<std::string>::iterator ite = comments.end();

                while( its != ite )
                {
                    op->AddComment( *its );
                    ++its;
                }
            }

            olnPlaceKeepout.push_back( op );

            return;
        }

        if( CompareToken( ".PLACE_REGION", token ) )
        {
            if( aBoardState != IDF3::FILE_OUTLINE )
                throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
                                  "invalid IDF file\n"
                                  "* Violation of specification: expecting .BOARD_OUTLINE, have "
                                  ".PLACE_REGION" ) );

            GROUP_OUTLINE* op = new GROUP_OUTLINE( this );

            if( op == nullptr )
                throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
                                  "could not create PLACE_REGION object" ) );

            op->SetUnit( unit );
            op->readData( aBoardFile, iline, idfVer );

            if( !comments.empty() )
            {
                std::list<std::string>::iterator its = comments.begin();
                std::list<std::string>::iterator ite = comments.end();

                while( its != ite )
                {
                    op->AddComment( *its );
                    ++its;
                }
            }

            olnGroup.insert( pair<string, GROUP_OUTLINE*>(op->GetGroupName(), op) );

            return;
        }

        if( CompareToken( ".DRILLED_HOLES", token ) )
        {
            if( aBoardState != IDF3::FILE_OUTLINE )
                throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
                                  "invalid IDF file\n"
                                  "* Violation of specification: expecting .BOARD_OUTLINE, have "
                                  ".DRILLED_HOLES" ) );

            readBrdDrills( aBoardFile, aBoardState );

            if( !comments.empty() )
            {
                std::list<std::string>::iterator its = comments.begin();
                std::list<std::string>::iterator ite = comments.end();

                while( its != ite )
                {
                    drillComments.push_back( *its );
                    ++its;
                }
            }

            return;
        }

        if( CompareToken( ".NOTES", token ) )
        {
            if( aBoardState != IDF3::FILE_OUTLINE )
                throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
                                  "invalid IDF file\n"
                                  "* Violation of specification: expecting .BOARD_OUTLINE, have "
                                  ".NOTES" ) );

            if( idfVer < IDF_V3 )
                throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
                                  "invalid IDFv2 file\n"
                                  "* Violation of specification: NOTES section not in "
                                  "specification" ) );

            readBrdNotes( aBoardFile, aBoardState );

            if( !comments.empty() )
            {
                std::list<std::string>::iterator its = comments.begin();
                std::list<std::string>::iterator ite = comments.end();

                while( its != ite )
                {
                    noteComments.push_back( *its );
                    ++its;
                }
            }

            return;
        }

        if( CompareToken( ".PLACEMENT", token ) )
        {
            if( aBoardState != IDF3::FILE_OUTLINE )
                throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
                                  "invalid IDF file\n"
                                  "* Violation of specification: expecting .BOARD_OUTLINE, have "
                                  ".PLACEMENT" ) );

            readBrdPlacement( aBoardFile, aBoardState, aNoSubstituteOutlines );

            if( !comments.empty() )
            {
                std::list<std::string>::iterator its = comments.begin();
                std::list<std::string>::iterator ite = comments.end();

                while( its != ite )
                {
                    placeComments.push_back( *its );
                    ++its;
                }
            }

            return;
        }
    }
}


void IDF3_BOARD::readBoardFile( const std::string& aFileName, bool aNoSubstituteOutlines )
{
    OPEN_ISTREAM( brd, aFileName.c_str() );

    try
    {
        brd.exceptions ( std::ios_base::badbit );

        if( brd.fail() )
        {
            ostringstream ostr;
            ostr << "\n* could not open file: '" << aFileName << "'";

            throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) );
        }

        brd.imbue( std::locale( "C" ) );
        std::string iline;      // the input line
        bool isComment;         // true if a line just read in is a comment line
        std::streampos pos;
        IDF3::FILE_STATE state = IDF3::FILE_START;

        // note: as per IDFv3 specification:
        //      "The Header section must be the first section in the file, the second
        //       section must be the Outline section, and the last section must be the
        //       Placement section. All other sections may be in any order."

        // further notes: Except for the HEADER section, sections may be preceded by
        // comment lines which will be copied back out on write(). No comments may
        // be associated with the board file itself since the only logical location
        // for unambiguous association is at the end of the file, which is inconvenient
        // for large files.

        readBrdHeader( brd, state );

        // read the various sections
        while( state != IDF3::FILE_PLACEMENT && brd.good() )
            readBrdSection( brd, state, aNoSubstituteOutlines );

        if( !brd.good() )
        {
            // check if we have valid data
            if( brd.eof() && state >= IDF3::FILE_OUTLINE && state < IDF3::FILE_INVALID )
            {
                CLOSE_STREAM( brd );
                return;
            }

            CLOSE_STREAM( brd );
            ostringstream ostr;
            ostr << "\n* empty IDF file: '" << aFileName << "'";

            throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) );
        }

        if( brd.good() && state == IDF3::FILE_PLACEMENT )
        {
            // read in any trailing lines and report on ignored comments (minor fault)
            // and any non-comment item (non-compliance with IDFv3)
            while( brd.good() )
            {
                while( !FetchIDFLine( brd, iline, isComment, pos ) && brd.good() );

                // normally this is a fault but we have all the data in accordance with specs
                if( ( !brd.good() && !brd.eof() ) || iline.empty() )
                    break;

                if( isComment )
                {
                    ERROR_IDF << "[warning]: trailing comments after PLACEMENT\n";
                }
                else
                {
                    ostringstream ostr;
                    ostr << "\n* problems reading file: '" << aFileName << "'";

                    throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
                                      "invalid IDF file\n"
                                      "* Violation of specification: non-comment lines after "
                                      "PLACEMENT section" ) );
                }
            }
        }
    }
    catch( const std::exception& )
    {
        brd.exceptions ( std::ios_base::goodbit );
        CLOSE_STREAM( brd );
        throw;
    }

    CLOSE_STREAM( brd );
    return;
}


void IDF3_BOARD::readLibSection( std::istream& aLibFile, IDF3::FILE_STATE& aLibState,
                                 IDF3_BOARD* aBoard )
{
    if( aBoard == nullptr )
    {
        throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
                          "\n* BUG: invoked with nullptr reference aBoard" ) );
    }

    std::list< std::string > comments;  // comments associated with a section

    // Reads in .ELECTRICAL, .MECHANICAL or #COMMENTS
    std::string iline;      // the input line
    bool isComment;         // true if a line just read in is a comment line
    std::streampos pos;
    int idx = 0;
    bool quoted = false;
    std::string token;
    IDF3_COMP_OUTLINE *pout = new IDF3_COMP_OUTLINE( this );

    if( !pout )
        throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
                          "\n* memory allocation failure" ) );

    while( aLibFile.good() )
    {
        while( !FetchIDFLine( aLibFile, iline, isComment, pos ) && aLibFile.good() );

        if( !aLibFile.good() && !aLibFile.eof() )
        {
            delete pout;
            throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
                              "problems reading library section" ) );
        }

        // no data was read; this only happens at eof()
        if( iline.empty() )
        {
            delete pout;
            return;
        }

        if( isComment )
        {
            comments.push_back( iline );
            continue;
        }

        // This must be a header
        GetIDFString( iline, token, quoted, idx );

        if( quoted )
        {
            ostringstream ostr;
            ostr << "invalid IDF library\n";
            ostr << "* Violation of specification: quoted string where .ELECTRICAL or "
                    ".MECHANICAL expected\n";
            ostr << "* line: '" << iline << "'\n";
            ostr << "* pos: " << pos;
            delete pout;

            throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) );
        }

        if( CompareToken( ".ELECTRICAL", token ) || CompareToken( ".MECHANICAL", token ) )
        {
            pout->readData( aLibFile, token, idfVer );

            if( !comments.empty() )
            {
                std::list<std::string>::iterator its = comments.begin();
                std::list<std::string>::iterator ite = comments.end();

                while( its != ite )
                {
                    pout->AddComment( *its );
                    ++its;
                }
            }

            IDF3_COMP_OUTLINE* cop = aBoard->GetComponentOutline( pout->GetUID() );

            if( cop == nullptr )
            {
                compOutlines.insert( pair<const std::string,
                                     IDF3_COMP_OUTLINE*>( pout->GetUID(), pout ) );
            }
            else
            {
                if( MatchCompOutline( pout, cop ) )
                {
                    delete pout;
                    // everything is fine; the outlines are genuine duplicates
                    return;
                }

                ostringstream ostr;
                ostr << "invalid IDF library\n";
                ostr << "duplicate Component Outline: '" << pout->GetUID() << "'\n";
                ostr << "* Violation of specification: multiple outlines have the same GEOM and "
                        "PART name\n";
                ostr << "* line: '" << iline << "'\n";
                ostr << "* pos: " << pos;
                delete pout;

                throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) );
            }

            return;
        }
        else
        {
            ostringstream ostr;
            ostr << "invalid IDF library\n";
            ostr << "* Expecting .ELECTRICAL or .MECHANICAL, got '" << token << "'\n";
            ostr << "* line: '" << iline << "'\n";
            ostr << "* pos: " << pos;
            delete pout;

            throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) );
        }
    }

    delete pout;

    if( !aLibFile.eof() )
        throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
                          "problems reading IDF library file" ) );

    return;
}


// read the library HEADER
void IDF3_BOARD::readLibHeader( std::istream& aLibFile, IDF3::FILE_STATE& aLibState )
{
    std::string iline;      // the input line
    bool isComment;         // true if a line just read in is a comment line
    std::streampos pos;
    int idx = 0;
    bool quoted = false;
    std::string token;

    // RECORD 1: ".HEADER" must be the very first line
    while( !FetchIDFLine( aLibFile, iline, isComment, pos ) && aLibFile.good() );

    if( !aLibFile.good() )
        throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
                          "invalid IDF library file\n"
                          "* premature end of file (no HEADER)" ) );

    if( isComment )
        throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
                          "invalid IDF library file\n"
                          "* Violation of specification: first line must be .HEADER" ) );

    if( !CompareToken( ".HEADER", iline ) )
        throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
                          "invalid IDF library file\n"
                          "* Violation of specification:\n"
                          "* first line must be .HEADER and have no quotes or trailing text" ) );

    // RECORD 2:
    //      File Type [str]: LIBRARY_FILE
    //      IDF Version Number [float]: must be 3.0
    //      Source System [str]: ignored
    //      Date [str]: ignored
    //      Library File Version [int]: ignored
    while( !FetchIDFLine( aLibFile, iline, isComment, pos ) && aLibFile.good() );

    if( !aLibFile.good() )
        throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
                          "invalid IDF library file\n"
                          "* premature end of HEADER" ) );

    if( isComment )
        throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
                          "invalid IDF library file\n"
                          "* Violation of specification: comment within .HEADER section" ) );

    idx = 0;
    GetIDFString( iline, token, quoted, idx );

    if( quoted )
        throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
                          "invalid IDF library file\n"
                          "* Violation of specification:\n"
                          "* file Type in HEADER section must not be in quotes" ) );

    if( !CompareToken( "LIBRARY_FILE", token ) )
    {
        ostringstream ostr;
        ostr << "invalid IDF library\n";
        ostr << "* Expecting string: LIBRARY_FILE (got '" << token << "')\n";

        throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) );
    }

    if( !GetIDFString( iline, token, quoted, idx ) )
        throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
                          "invalid IDF library file\n"
                          "* Violation of specification: HEADER section, RECORD 2: no FIELD 2" ) );

    if( quoted )
        throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
                          "invalid IDF library file\n"
                          "* Violation of specification: IDF Version must not be in quotes" ) );

    if( !token.compare( "3.0" ) || !token.compare( "3." ) || !token.compare( "3" ) )
    {
        idfVer = IDF_V3;
    }
    else if( !token.compare( "2.0" ) || !token.compare( "2." ) || !token.compare( "2" ) )
    {
        idfVer = IDF_V2;
    }
    else
    {
        ostringstream ostr;

        ostr << "unsupported IDF version\n";
        ostr << "* Expecting version to be a variant of '3.0', '2.0' (value: '" << token << "')\n";

        throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) );
    }

    if( !GetIDFString( iline, token, quoted, idx ) )
        throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
                          "invalid IDF library file\n"
                          "* Violation of specification:\n"
                          "* HEADER section, RECORD 2, FIELD 3: no Source System string" ) );

    libSource = token;

    if( !GetIDFString( iline, token, quoted, idx ) )
        throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
                          "invalid IDF library file\n"
                          "* Violation of specification:\n"
                          "* HEADER section, RECORD 2, FIELD 4: no Date string" ) );

    libDate = token;

    if( !GetIDFString( iline, token, quoted, idx ) )
        throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
                          "invalid IDF library file\n"
                          "* Violation of specification:\n"
                          "* HEADER section, RECORD 2, FIELD 5: no Board File Version number" ) );

    std::istringstream istr;
    istr.str( token );

    istr >> libFileVersion;

    if( istr.fail() )
    {
        ERROR_IDF << "invalid Library File Version in header\n";
        cerr << "* Setting default version of 1\n";
        libFileVersion = 1;
    }

    if( quoted )
        throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
                          "invalid IDF library file\n"
                          "* Violation of specification:\n"
                          "* HEADER section, RECORD 2, FIELD 5: Library File Version must not "
                          "be in quotes" ) );

    // RECORD 3:
    //      .END_HEADER
    while( !FetchIDFLine( aLibFile, iline, isComment, pos ) && aLibFile.good() );

    if( ( !aLibFile.good() && !aLibFile.eof() ) || iline.empty() )
        throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
                          "problems reading library header, RECORD 3" ) );

    if( isComment )
        throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
                          "invalid IDF library file\n"
                          "* Violation of specification: comment within .HEADER section" ) );

    if( !CompareToken( ".END_HEADER", iline ) )
    {
        ostringstream ostr;
        ostr << "invalid IDF header\n";
        ostr << "* Violation of specification: expected .END_HEADER (got '" << iline << "')\n";

        throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) );
    }

    aLibState = IDF3::FILE_HEADER;
}


void IDF3_BOARD::readLibFile( const std::string& aFileName )
{
    OPEN_ISTREAM( lib, aFileName.c_str() );

    try
    {
        lib.exceptions ( std::ios_base::badbit );

        if( lib.fail() )
        {
            ostringstream ostr;
            ostr << "\n* could not open file: '" << aFileName << "'";

            throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) );
        }

        lib.imbue( std::locale( "C" ) );
        IDF3::FILE_STATE state = IDF3::FILE_START;

        readLibHeader( lib, state );

        while( lib.good() ) readLibSection( lib, state, this );
    }
    catch( const std::exception& )
    {
        lib.exceptions ( std::ios_base::goodbit );
        CLOSE_STREAM( lib );
        throw;
    }

    CLOSE_STREAM( lib );
}


bool IDF3_BOARD::ReadFile( const wxString& aFullFileName, bool aNoSubstituteOutlines )
{
    // 1. Check that the file extension is 'emn'
    // 2. Check if a file with extension 'emp' exists and read it
    // 3. Open the specified filename and read it

    wxFileName brdname( aFullFileName );
    wxFileName libname( aFullFileName );
    wxString ext = brdname.GetExt();

    if( !ext.Cmp( "EMN" ) )
    {
        libname.SetExt( wxT( "EMP" ) );
    }
    else if( !ext.Cmp( "emn" ) )
    {
        libname.SetExt( wxT( "emp" ) );
    }
    else
    {
        ostringstream ostr;
        ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
        ostr << "* [INFO] invalid file name: '" << aFullFileName.ToUTF8() << "'";

        errormsg = ostr.str();
    }


    brdname.SetExt( wxT( "emn" ) );

    std::string bfname = TO_UTF8( aFullFileName );

    if( !wxFileExists( bfname ) )
    {
        brdname.SetExt( wxT( "EMN" ) );
        libname.SetExt( wxT( "EMP" ) );
    }

    try
    {
        if( !brdname.IsOk() )
        {
            ostringstream ostr;
            ostr << "\n* invalid file name: '" << aFullFileName.ToUTF8() << "'";

            throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) );
        }

        if( !brdname.FileExists() )
        {
            ostringstream ostr;
            ostr << "\n* no such file: '" << aFullFileName.ToUTF8() << "'";

            throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) );
        }

        if( !brdname.IsFileReadable() )
        {
            ostringstream ostr;
            ostr << "\n* cannot read file: '" << bfname << "'";

            throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) );
        }

        bfname = TO_UTF8( brdname.GetFullPath() );
        std::string lfname = TO_UTF8( libname.GetFullPath() );

        if( !libname.FileExists() )
        {
            // NOTE: Since this is a common case we simply proceed
            // with the assumption that there is no library file;
            // however we print a message to inform the user.
            ERROR_IDF;
            cerr << "no associated library file (*.emp)\n";
        }
        else if( !libname.IsFileReadable() )
        {
            ostringstream ostr;
            ostr << "\n* cannot read library file: '" << lfname << "'";

            throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) );
        }
        else
        {
            // read the library file before proceeding
            readLibFile( lfname );
        }

        // read the board file
        readBoardFile( bfname, aNoSubstituteOutlines );
    }
    catch( const std::exception& e )
    {
        Clear();
        errormsg = e.what();

        return false;
    }

    return true;
}


bool IDF3_BOARD::writeLibFile( const std::string& aFileName )
{
    OPEN_OSTREAM( lib, aFileName.c_str() );

    try
    {
        lib.exceptions( std::ios_base::failbit );

        if( lib.fail() )
        {
            ostringstream ostr;
            ostr << "\n* could not open file: '" << aFileName << "'";

            throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) );
        }

        lib.imbue( std::locale( "C" ) );
        wxDateTime tdate( time( nullptr ) );

        if( idfSource.empty() )
            idfSource = "KiCad-IDF Framework";

        ostringstream fileDate;
        fileDate << setfill( '0' ) << setw(4) << tdate.GetYear();
        fileDate << "/" << setw(2) << tdate.GetMonth() << "/" << tdate.GetDay();
        fileDate << "." << tdate.GetHour() << ":" << tdate.GetMinute() << ":" << tdate.GetSecond();
        libDate = fileDate.str();

        lib << ".HEADER\n";
        lib << "LIBRARY_FILE 3.0 \"Created by " << idfSource;
        lib << "\" " << libDate << " " << (++libFileVersion) << "\n";
        lib << ".END_HEADER\n\n";

        std::map< std::string, IDF3_COMP_OUTLINE*>::iterator its = compOutlines.begin();
        std::map< std::string, IDF3_COMP_OUTLINE*>::iterator ite = compOutlines.end();

        while( its != ite )
        {
            its->second->writeData( lib );
            ++its;
        }

    }
    catch( const std::exception& )
    {
        lib.exceptions( std::ios_base::goodbit );
        CLOSE_STREAM( lib );
        throw;
    }

    CLOSE_STREAM( lib );
    return true;
}


void IDF3_BOARD::writeBoardFile( const std::string& aFileName )
{
    OPEN_OSTREAM( brd, aFileName.c_str() );

    try
    {
        brd.exceptions( std::ostream::failbit );

        if( brd.fail() )
        {
            ostringstream ostr;
            ostr << "\n* could not open file: '" << aFileName << "'";

            throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) );
        }

        brd.imbue( std::locale( "C" ) );
        wxDateTime tdate( time( nullptr ) );

        if( idfSource.empty() )
            idfSource = "KiCad-IDF Framework";

        ostringstream fileDate;
        fileDate << setfill( '0' ) << setw(4) << tdate.GetYear();
        fileDate << "/" << setw(2) << tdate.GetMonth() << "/" << tdate.GetDay();
        fileDate << "." << tdate.GetHour() << ":" << tdate.GetMinute() << ":" << tdate.GetSecond();
        brdDate = fileDate.str();

        brd << ".HEADER\n";
        brd << "BOARD_FILE 3.0 \"Created by " << idfSource;
        brd << "\" " << brdDate << " " << (++brdFileVersion) << "\n";

        if( boardName.empty() )
            brd << "\"BOARD WITH NO NAME\" ";
        else
            brd << "\"" << boardName << "\" ";

        brd << setw(1) << setfill( ' ' );

        if( unit == IDF3::UNIT_MM )
            brd << "MM\n";
        else
            brd << "THOU\n";

        brd << ".END_HEADER\n\n";

        // write the BOARD_OUTLINE
        olnBoard.writeData( brd );

        // OTHER outlines
        do
        {
            std::map<std::string, OTHER_OUTLINE*>::iterator its = olnOther.begin();
            std::map<std::string, OTHER_OUTLINE*>::iterator ite = olnOther.end();

            while(its != ite )
            {
                its->second->writeData( brd );
                ++its;
            }

        } while( 0 );

        // ROUTE outlines
        do
        {
            std::list<ROUTE_OUTLINE*>::iterator its = olnRoute.begin();
            std::list<ROUTE_OUTLINE*>::iterator ite = olnRoute.end();

            while( its != ite )
            {
                (*its)->writeData( brd );
                ++its;
            }

        } while( 0 );

        // PLACEMENT outlines
        do
        {
            std::list<PLACE_OUTLINE*>::iterator its = olnPlace.begin();
            std::list<PLACE_OUTLINE*>::iterator ite = olnPlace.end();

            while( its != ite )
            {
                (*its)->writeData( brd );
                ++its;
            }

        } while( 0 );

        // ROUTE KEEPOUT outlines
        do
        {
            std::list<ROUTE_KO_OUTLINE*>::iterator its = olnRouteKeepout.begin();
            std::list<ROUTE_KO_OUTLINE*>::iterator ite = olnRouteKeepout.end();

            while( its != ite )
            {
                (*its)->writeData( brd );
                ++its;
            }

        } while( 0 );

        // VIA KEEPOUT outlines
        do
        {
            std::list<VIA_KO_OUTLINE*>::iterator its = olnViaKeepout.begin();
            std::list<VIA_KO_OUTLINE*>::iterator ite = olnViaKeepout.end();

            while( its != ite )
            {
                (*its)->writeData( brd );
                ++its;
            }

        } while( 0 );

        // PLACE KEEPOUT outlines
        do
        {
            std::list<PLACE_KO_OUTLINE*>::iterator its = olnPlaceKeepout.begin();
            std::list<PLACE_KO_OUTLINE*>::iterator ite = olnPlaceKeepout.end();

            while( its != ite )
            {
                (*its)->writeData( brd );
                ++its;
            }

        } while( 0 );

        // PLACEMENT GROUP outlines
        do
        {
            std::multimap<std::string, GROUP_OUTLINE*>::iterator its = olnGroup.begin();
            std::multimap<std::string, GROUP_OUTLINE*>::iterator ite = olnGroup.end();

            while( its != ite )
            {
                its->second->writeData( brd );
                ++its;
            }

        } while( 0 );

        // Drilled holes
        do
        {
            std::list<std::string>::iterator itds = drillComments.begin();
            std::list<std::string>::iterator itde = drillComments.end();

            while( itds != itde )
            {
                brd << "# " << *itds << "\n";
                ++itds;
            }

            brd << ".DRILLED_HOLES\n";

            std::list<IDF_DRILL_DATA*>::iterator itbs = board_drills.begin();
            std::list<IDF_DRILL_DATA*>::iterator itbe = board_drills.end();

            while( itbs != itbe )
            {
                (*itbs)->write( brd, unit );
                ++itbs;
            }

            std::map< std::string, IDF3_COMPONENT*>::iterator itcs = components.begin();
            std::map< std::string, IDF3_COMPONENT*>::iterator itce = components.end();

            while( itcs != itce )
            {
                itcs->second->writeDrillData( brd );
                ++itcs;
            }

            brd << ".END_DRILLED_HOLES\n\n";
        } while( 0 );

        // Notes
        if( !notes.empty() )
        {
            std::list<std::string>::iterator itncs = noteComments.begin();
            std::list<std::string>::iterator itnce = noteComments.end();

            while( itncs != itnce )
            {
                brd << "# " << *itncs << "\n";
                ++itncs;
            }

            brd << ".NOTES\n";

            std::list<IDF_NOTE*>::iterator itns = notes.begin();
            std::list<IDF_NOTE*>::iterator itne = notes.end();

            while( itns != itne )
            {
                (*itns)->writeNote( brd, unit );
                ++itns;
            }

            brd << ".END_NOTES\n\n";

        }

        // Placement
        if( !components.empty() )
        {
            std::list<std::string>::iterator itpcs = placeComments.begin();
            std::list<std::string>::iterator itpce = placeComments.end();

            while( itpcs != itpce )
            {
                brd << "# " << *itpcs << "\n";
                ++itpcs;
            }

            std::map< std::string, IDF3_COMPONENT*>::iterator itcs = components.begin();
            std::map< std::string, IDF3_COMPONENT*>::iterator itce = components.end();

            // determine if there are any component outlines at all and avoid
            // writing an empty PLACEMENT section if there are no outlines.
            // this will cost a little time but prevents software such as
            // CircuitWorks from segfaulting on an empty section.

            bool hasOutlines = false;

            while( itcs != itce )
            {
                if( itcs->second->GetOutlinesSize() > 0 )
                {
                    itcs = components.begin();
                    hasOutlines = true;
                    break;
                }

                ++itcs;
            }

            if( hasOutlines )
            {
                brd << ".PLACEMENT\n";

                while( itcs != itce )
                {
                    itcs->second->writePlaceData( brd );
                    ++itcs;
                }

                brd << ".END_PLACEMENT\n";
            }

        }

    }
    catch( const std::exception& )
    {
        brd.exceptions( std::ios_base::goodbit );
        CLOSE_STREAM( brd );
        throw;
    }

    CLOSE_STREAM( brd );
    return;
}


bool IDF3_BOARD::WriteFile( const wxString& aFullFileName, bool aUnitMM, bool aForceUnitFlag )
{
    if( aUnitMM != IDF3::UNIT_THOU )
        setUnit( IDF3::UNIT_MM, aForceUnitFlag );
    else
        setUnit( IDF3::UNIT_THOU, aForceUnitFlag );

    // 1. Check that the file extension is 'emn'
    // 2. Write the *.emn file according to the IDFv3 spec
    // 3. Write the *.emp file according to the IDFv3 spec

    wxFileName brdname( aFullFileName );
    wxFileName libname( aFullFileName );

    brdname.SetExt( wxT( "emn" ) );
    libname.SetExt( wxT( "emp" ) );

    std::string bfname = TO_UTF8( aFullFileName );

    try
    {
        if( !brdname.IsOk() )
        {
            ostringstream ostr;
            ostr << "\n* invalid file name: '" << bfname << "'";

            throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) );
        }

        if( brdname.FileExists() && !brdname.IsFileWritable() )
        {
            ostringstream ostr;
            ostr << "cannot overwrite existing board file\n";
            ostr << "* filename: '" << bfname << "'";

            throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) );
        }

        bfname = TO_UTF8( brdname.GetFullPath() );
        std::string lfname = TO_UTF8( libname.GetFullPath() );

        if( libname.FileExists() && !libname.IsFileWritable() )
        {
            ostringstream ostr;
            ostr << "cannot overwrite existing library file\n";
            ostr << "* filename: '" << lfname << "'";

            throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) );
        }

        writeLibFile( lfname );
        writeBoardFile( bfname );

    }
    catch( const std::exception& e )
    {
        errormsg = e.what();

        return false;
    }

    return true;
}


const std::string& IDF3_BOARD::GetIDFSource( void )
{
    return idfSource;
}


void IDF3_BOARD::SetIDFSource( const std::string& aIDFSource )
{
    idfSource = aIDFSource;
    return;
}


const std::string& IDF3_BOARD::GetBoardSource( void )
{
    return brdSource;
}


const std::string& IDF3_BOARD::GetLibrarySource( void )
{
    return libSource;
}


const std::string& IDF3_BOARD::GetBoardDate( void )
{
    return brdDate;
}


const std::string& IDF3_BOARD::GetLibraryDate( void )
{
    return libDate;
}


int IDF3_BOARD::GetBoardVersion( void )
{
    return brdFileVersion;
}


bool IDF3_BOARD::SetBoardVersion( int aVersion )
{
    if( aVersion < 0 )
    {
        ostringstream ostr;
        ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "():\n";
        ostr << "*  board version (" << aVersion << ") must be >= 0";
        errormsg = ostr.str();

        return false;
    }

    brdFileVersion = aVersion;

    return true;
}


int IDF3_BOARD::GetLibraryVersion( void )
{
    return libFileVersion;
}


bool IDF3_BOARD::SetLibraryVersion( int aVersion )
{
    if( aVersion < 0 )
    {
        ostringstream ostr;
        ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "():\n";
        ostr << "* library version (" << aVersion << ") must be >= 0";
        errormsg = ostr.str();

        return false;
    }

    libFileVersion = aVersion;

    return true;
}


double IDF3_BOARD::GetUserScale( void )
{
    return userScale;
}


bool IDF3_BOARD::SetUserScale( double aScaleFactor )
{
    if( aScaleFactor == 0.0 )
    {
        ostringstream ostr;
        ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "():\n";
        ostr << "* BUG: user scale factor must not be 0";
        errormsg = ostr.str();

        return false;
    }

    userScale = aScaleFactor;
    return true;
}


int IDF3_BOARD::GetUserPrecision( void )
{
    return userPrec;
}


bool IDF3_BOARD::SetUserPrecision( int aPrecision )
{
    if( aPrecision < 1 || aPrecision > 8 )
    {
        ostringstream ostr;
        ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "():\n";
        ostr << "* precision value (" << aPrecision << ") must be 1..8";
        errormsg = ostr.str();

        return false;
    }

    userPrec = aPrecision;
    return true;
}


void IDF3_BOARD::GetUserOffset( double& aXoff, double& aYoff )
{
    aXoff = userXoff;
    aYoff = userYoff;
}


void IDF3_BOARD::SetUserOffset( double aXoff, double aYoff )
{
    userXoff = aXoff;
    userYoff = aYoff;
}


bool IDF3_BOARD::AddBoardOutline( IDF_OUTLINE* aOutline )
{
    if( !olnBoard.AddOutline( aOutline ) )
    {
        errormsg = olnBoard.GetError();

        return false;
    }

    return true;
}


bool IDF3_BOARD::DelBoardOutline( IDF_OUTLINE* aOutline )
{
    if( !olnBoard.DelOutline( aOutline ) )
    {
        errormsg = olnBoard.GetError();
        return false;
    }

    return true;
}


bool IDF3_BOARD::DelBoardOutline( size_t aIndex )
{
    if( !olnBoard.DelOutline( aIndex ) )
    {
        errormsg = olnBoard.GetError();
        return false;
    }

    return true;
}


size_t IDF3_BOARD::GetBoardOutlinesSize( void )
{
    return olnBoard.OutlinesSize();
}


BOARD_OUTLINE* IDF3_BOARD::GetBoardOutline( void )
{
    return &olnBoard;
}


const std::list< IDF_OUTLINE* >*const IDF3_BOARD::GetBoardOutlines( void )
{
    return olnBoard.GetOutlines();
}


IDF_DRILL_DATA* IDF3_BOARD::AddBoardDrill( double aDia, double aXpos, double aYpos,
                                           IDF3::KEY_PLATING aPlating,
                                           const std::string& aHoleType,
                                           IDF3::KEY_OWNER aOwner )
{
    IDF_DRILL_DATA* drill = new IDF_DRILL_DATA( aDia, aXpos, aYpos, aPlating,
                                                "BOARD", aHoleType, aOwner );

    if( drill != nullptr )
        board_drills.push_back( drill );

    return drill;
}


IDF_DRILL_DATA* IDF3_BOARD::AddDrill( IDF_DRILL_DATA* aDrilledHole )
{
    if( !aDrilledHole )
        return nullptr;

    // note: PANEL drills are essentially BOARD drills which
    // the panel requires to be present
    if( CompareToken( "BOARD", aDrilledHole->GetDrillRefDes() )
        || CompareToken( "PANEL", aDrilledHole->GetDrillRefDes() ) )
    {
        board_drills.push_back( aDrilledHole );
        return aDrilledHole;
    }

    return addCompDrill( aDrilledHole );
}


bool IDF3_BOARD::DelBoardDrill( double aDia, double aXpos, double aYpos )
{
    errormsg.clear();

    std::list<IDF_DRILL_DATA*>::iterator sp = board_drills.begin();
    std::list<IDF_DRILL_DATA*>::iterator ep = board_drills.end();
    bool rval = false;

    while( sp != ep )
    {
        if( (*sp)->Matches( aDia, aXpos, aYpos ) )
        {
#ifndef DISABLE_IDF_OWNERSHIP
            IDF3::KEY_OWNER keyo = (*sp)->GetDrillOwner();

            if( keyo == UNOWNED || ( keyo == MCAD && cadType == CAD_MECH )
                || ( keyo == ECAD && cadType == CAD_ELEC ) )
            {
                rval = true;
                delete *sp;
                sp = board_drills.erase( sp );
                continue;
            }
            else
            {
                ostringstream ostr;
                ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "():\n";
                ostr << "* ownership violation; drill owner (";

                switch( keyo )
                {
                case ECAD:
                    ostr << "ECAD";
                    break;

                case MCAD:
                    ostr << "MCAD";
                    break;

                default:
                    ostr << "invalid: " << keyo;
                    break;
                }

                ostr << ") may not be modified by ";

                if( cadType == CAD_MECH )
                    ostr << "MCAD";
                else
                    ostr << "ECAD";

                errormsg = ostr.str();

                ++sp;
                continue;
            }
#else
            rval = true;
            delete *sp;
            sp = board_drills.erase( sp );
            continue;
#endif
        }

        ++sp;
    }

    return rval;
}


bool IDF3_BOARD::AddSlot( double aWidth, double aLength, double aOrientation, double aX, double aY )
{
    if( aWidth < IDF_MIN_DIA_MM )
    {
        ostringstream ostr;
        ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "():\n";
        ostr << "* slot width (" << aWidth << ") must be >= " << IDF_MIN_DIA_MM;
        errormsg = ostr.str();

        return false;
    }

    if( aLength < IDF_MIN_DIA_MM )
    {
        ostringstream ostr;
        ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "():\n";
        ostr << "* slot length (" << aLength << ") must be >= " << IDF_MIN_DIA_MM;
        errormsg = ostr.str();

        return false;
    }

    IDF_POINT c[2];     // centers
    IDF_POINT pt[4];

    double a1 = aOrientation / 180.0 * M_PI;
    double a2 = a1 + M_PI_2;
    double d1 = aLength / 2.0;
    double d2 = aWidth / 2.0;
    double sa1 = sin( a1 );
    double ca1 = cos( a1 );
    double dsa2 = d2 * sin( a2 );
    double dca2 = d2 * cos( a2 );

    c[0].x  = aX + d1 * ca1;
    c[0].y  = aY + d1 * sa1;

    c[1].x  = aX - d1 * ca1;
    c[1].y  = aY - d1 * sa1;

    pt[0].x = c[0].x - dca2;
    pt[0].y = c[0].y - dsa2;

    pt[1].x = c[1].x - dca2;
    pt[1].y = c[1].y - dsa2;

    pt[2].x = c[1].x + dca2;
    pt[2].y = c[1].y + dsa2;

    pt[3].x = c[0].x + dca2;
    pt[3].y = c[0].y + dsa2;

    IDF_OUTLINE* outline = new IDF_OUTLINE;

    if( outline == nullptr )
    {
        ostringstream ostr;
        ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "():\n";
        ostr << "* could not create an outline object";
        errormsg = ostr.str();

        return false;
    }

    // first straight run
    IDF_SEGMENT* seg = new IDF_SEGMENT( pt[0], pt[1] );
    outline->push( seg );

    // first 180 degree cap
    seg = new IDF_SEGMENT( c[1], pt[1], -180.0, true );
    outline->push( seg );

    // final straight run
    seg = new IDF_SEGMENT( pt[2], pt[3] );
    outline->push( seg );

    // final 180 degree cap
    seg = new IDF_SEGMENT( c[0], pt[3], -180.0, true );
    outline->push( seg );

    if( !olnBoard.addOutline( outline ) )
    {
        errormsg = olnBoard.GetError();
        return false;
    }

    return true;
}


IDF_DRILL_DATA* IDF3_BOARD::addCompDrill( double aDia, double aXpos, double aYpos,
                                          IDF3::KEY_PLATING aPlating,
                                          const std::string& aHoleType,
                                          IDF3::KEY_OWNER aOwner,
                                          const std::string& aRefDes )
{
    // first find the matching component; if it doesn't exist we must create it somehow -
    // question is, do we need a component outline at this stage or can those be added later?
    //
    // Presumably we can create a component with no outline and add the outlines later.
    // If a component is created and an outline specified but the outline is not loaded,
    // we're screwed if (a) we have already read the library file (*.emp) or (b) we don't
    // know the filename

    std::string refdes = aRefDes;

    // note: for components 'NOREFDES' would be assigned a Unique ID, but for holes
    // there is no way of associating the hole with the correct entity (if any)
    // so a hole added with "NOREFDES" goes to a generic component "NOREFDES"
    if( refdes.empty() )
        refdes = "NOREFDES";

    // check if the target is BOARD or PANEL
    if( CompareToken( "BOARD", refdes ) )
        return AddBoardDrill( aDia, aXpos, aYpos, aPlating, aHoleType, aOwner );

    if( CompareToken( "PANEL", refdes ) )
    {
        ostringstream ostr;
        ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "():\n";
        ostr << "* PANEL data not supported";
        errormsg = ostr.str();

        return nullptr;
    }

    std::map<std::string, IDF3_COMPONENT*>::iterator ref = components.find( refdes );

    if( ref == components.end() )
    {
        // create the item
        IDF3_COMPONENT* comp = new IDF3_COMPONENT( this );

        if( comp == nullptr )
        {
            ostringstream ostr;
            ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "():\n";
            ostr << "* could not create new component object";
            errormsg = ostr.str();

            return nullptr;
        }

        comp->SetParent( this );
        comp->SetRefDes( refdes );
        ref = components.insert( std::pair< std::string,
                                 IDF3_COMPONENT*> ( comp->GetRefDes(), comp ) ).first;
    }

    // add the drill
    IDF_DRILL_DATA* dp = ref->second->AddDrill( aDia, aXpos, aYpos, aPlating, aHoleType, aOwner );

    if( !dp )
    {
        errormsg = ref->second->GetError();
        return nullptr;
    }

    return dp;
}


IDF_DRILL_DATA* IDF3_BOARD::addCompDrill( IDF_DRILL_DATA* aDrilledHole )
{
    if( !aDrilledHole )
    {
        ostringstream ostr;
        ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "(): nullptr pointer";
        errormsg = ostr.str();

        return nullptr;
    }

    if( CompareToken( "PANEL", aDrilledHole->GetDrillRefDes() ) )
    {
        ostringstream ostr;
        ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "():\n";
        ostr << "* PANEL data not supported";
        errormsg = ostr.str();

        return nullptr;
    }

    std::map<std::string, IDF3_COMPONENT*>::iterator ref =
            components.find( aDrilledHole->GetDrillRefDes() );

    if( ref == components.end() )
    {
        // create the item
        IDF3_COMPONENT* comp = new IDF3_COMPONENT( this );

        if( comp == nullptr )
        {
            ostringstream ostr;
            ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "():\n";
            ostr << "* could not create new component object";
            errormsg = ostr.str();

            return nullptr;
        }

        comp->SetParent( this );
        comp->SetRefDes( aDrilledHole->GetDrillRefDes() );
        ref = components.insert( std::pair< std::string,
                                 IDF3_COMPONENT*> ( comp->GetRefDes(), comp ) ).first;
    }

    IDF_DRILL_DATA* dp = ref->second->AddDrill( aDrilledHole );

    if( !dp )
    {
        errormsg = ref->second->GetError();
        return nullptr;
    }

    return dp;
}


bool IDF3_BOARD::delCompDrill( double aDia, double aXpos, double aYpos, const std::string& aRefDes )
{
    errormsg.clear();

    std::map<std::string, IDF3_COMPONENT*>::iterator ref = components.find( aRefDes );

    if( ref == components.end() )
        return false;

    if( !ref->second->DelDrill( aDia, aXpos, aYpos ) )
    {
        errormsg = ref->second->GetError();
        return false;
    }

    return true;
}


bool IDF3_BOARD::AddComponent( IDF3_COMPONENT* aComponent )
{
    if( !aComponent )
    {
        ostringstream ostr;
        ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__;
        ostr << "(): Invalid component pointer (nullptr)";
        errormsg = ostr.str();

        return false;
    }

    if( components.insert( std::pair<std::string, IDF3_COMPONENT*>
        ( aComponent->GetRefDes(), aComponent ) ).second == false )
    {
        ostringstream ostr;
        ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "(): \n";
        ostr << "* duplicate RefDes ('" << aComponent->GetRefDes() << "')";
        errormsg = ostr.str();

        return false;
    }

    return true;
}


bool IDF3_BOARD::DelComponent( IDF3_COMPONENT* aComponent )
{
    errormsg.clear();

#ifndef DISABLE_IDF_OWNERSHIP
    if( !checkComponentOwnership( __LINE__, __FUNCTION__, aComponent ) )
        return false;
#endif

    std::map<std::string, IDF3_COMPONENT*>::iterator it =
        components.find( aComponent->GetRefDes() );

    if( it == components.end() )
        return false;

    delete it->second;
    components.erase( it );

    return true;
}


bool IDF3_BOARD::DelComponent( size_t aIndex )
{
    if( aIndex >= components.size() )
    {
        ostringstream ostr;
        ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "(): \n";
        ostr << "* aIndex (" << aIndex << ") out of range (" << components.size() << ")";
        errormsg = ostr.str();

        return false;
    }

    std::map<std::string, IDF3_COMPONENT*>::iterator it = components.begin();

    while( aIndex-- > 0 ) ++it;

#ifndef DISABLE_IDF_OWNERSHIP
    if( !checkComponentOwnership( __LINE__, __FUNCTION__, it->second ) )
        return false;
#endif

    delete it->second;
    components.erase( it );

    return true;
}


size_t IDF3_BOARD::GetComponentsSize( void )
{
    return components.size();
}


std::map< std::string, IDF3_COMPONENT* >*const IDF3_BOARD::GetComponents( void )
{
    return &components;
}


IDF3_COMPONENT* IDF3_BOARD::FindComponent( const std::string& aRefDes )
{
    std::map<std::string, IDF3_COMPONENT*>::iterator it = components.find( aRefDes );

    if( it == components.end() )
        return nullptr;

    return it->second;
}


IDF3_COMP_OUTLINE* IDF3_BOARD::GetComponentOutline( const wxString& aFullFileName )
{
    std::string fname = TO_UTF8( aFullFileName );
    wxFileName idflib( aFullFileName );

    if( !idflib.IsOk() )
    {
        ostringstream ostr;
        ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "(): \n";
        cerr << "* invalid file name: '" << fname << "'";
        errormsg = ostr.str();

        return nullptr;
    }

    if( !idflib.FileExists() )
    {
        ostringstream ostr;
        ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "(): \n";
        cerr << "* no such file: '" << fname  << "'";
        errormsg = ostr.str();

        return nullptr;
    }

    if( !idflib.IsFileReadable() )
    {
        ostringstream ostr;
        ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "(): \n";
        cerr << "* cannot read file: '" << fname << "'";
        errormsg = ostr.str();

        return nullptr;
    }

    std::map< std::string, std::string >::iterator itm = uidFileList.find( fname );

    if( itm != uidFileList.end() )
        return GetComponentOutline( itm->second );

    IDF3_COMP_OUTLINE* cp = new IDF3_COMP_OUTLINE( this );

    if( cp == nullptr )
    {
        ostringstream ostr;
        ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "(): \n";
        cerr << "* failed to create outline\n";
        cerr << "* filename: '" << fname << "'";
        errormsg = ostr.str();

        return nullptr;
    }

    OPEN_ISTREAM( model, fname.c_str() );

    try
    {
        model.exceptions ( std::ios_base::badbit );

        if( model.fail() )
        {
            ostringstream ostr;
            ostr << "\n* could not open file: '" << fname << "'";

            throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) );
        }

        model.imbue( std::locale( "C" ) );
        std::string iline;      // the input line
        bool isComment;         // true if a line just read in is a comment line
        std::streampos pos;

        while( true )
        {
            while( !FetchIDFLine( model, iline, isComment, pos ) && model.good() );

            if( !model.good() )
            {
                ostringstream ostr;
                ostr << "\n* problems reading file: '" << fname << "'";
                throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) );
            }

            // accept comment lines, .ELECTRICAL, or .MECHANICAL only
            if( isComment )
            {
                cp->AddComment( iline );
                continue;
            }

            if( CompareToken( ".ELECTRICAL", iline ) || CompareToken( ".MECHANICAL", iline ) )
            {
                cp->readData( model, iline, idfVer );
                break;
            }
            else
            {
                ostringstream ostr;
                ostr << "faulty IDF component definition\n";
                ostr << "* Expecting .ELECTRICAL or .MECHANICAL, got '" << iline << "'\n";
                cerr << "* File: '" << fname << "'\n";

                throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) );
            }
        }
    }
    catch( const std::exception& e )
    {
        delete cp;
        model.exceptions ( std::ios_base::goodbit );
        CLOSE_STREAM( model );
        errormsg = e.what();
        return nullptr;
    }

    CLOSE_STREAM( model );

    // check the unique ID against the list from library components
    std::list< std::string >::iterator lsts = uidLibList.begin();
    std::list< std::string >::iterator lste = uidLibList.end();
    std::string uid = cp->GetUID();
    IDF3_COMP_OUTLINE* oldp = nullptr;

    while( lsts != lste )
    {
        if( ! lsts->compare( uid ) )
        {
            oldp = GetComponentOutline( uid );

            if( MatchCompOutline( cp, oldp ) )
            {
                // everything is fine; the outlines are genuine duplicates; delete the copy
                delete cp;

                // make sure we can find the item via its filename
                uidFileList.insert( std::pair< std::string, std::string>( fname, uid ) );

                // return the pointer to the original
                return oldp;
            }
            else
            {
                delete cp;
                ostringstream ostr;
                ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "():\n";
                ostr << "* duplicate UID for different Component Outlines: '" << uid << "'\n";
                ostr << "* original loaded from library, duplicate in current file\n";
                ostr << "* file: '" << fname << "'";

                errormsg = ostr.str();
                return nullptr;
            }
        }

        ++lsts;
    }

    // if we got this far then any duplicates are from files previously read
    oldp = GetComponentOutline( uid );

    if( oldp == nullptr )
    {
        // everything is fine, there are no existing entries
        uidFileList.insert( std::pair< std::string, std::string>( fname, uid ) );
        compOutlines.insert( pair<const std::string, IDF3_COMP_OUTLINE*>( uid, cp ) );

        return cp;
    }

    if( MatchCompOutline( cp, oldp ) )
    {
        // everything is fine; the outlines are genuine duplicates; delete the copy
        delete cp;

        // make sure we can find the item via its other filename
        uidFileList.insert( std::pair< std::string, std::string>( fname, uid ) );

        // return the pointer to the original
        return oldp;
    }

    delete cp;

    // determine the file name of the first instance
    std::map< std::string, std::string >::iterator ufls = uidFileList.begin();
    std::map< std::string, std::string >::iterator ufle = uidFileList.end();
    std::string oldfname;

    while( ufls != ufle )
    {
        if( ! ufls->second.compare( uid ) )
        {
            oldfname = ufls->first;
            break;
        }

        ++ufls;
    }

    ostringstream ostr;
    ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "():\n";
    ostr << "* duplicate UID for different Component Outlines: '" << uid << "'\n";
    ostr << "* original file: '" << oldfname << "'\n";
    ostr << "* this file: '" << fname << "'";

    errormsg = ostr.str();
    return nullptr;
}


IDF3_COMP_OUTLINE* IDF3_BOARD::GetComponentOutline( const std::string& aComponentID )
{
    std::map< std::string, IDF3_COMP_OUTLINE*>::iterator its = compOutlines.find( aComponentID );

    if( its != compOutlines.end() )
        return its->second;

    return nullptr;
}


IDF3_COMP_OUTLINE* IDF3_BOARD::GetInvalidOutline( const std::string& aGeomName,
                                                  const std::string& aPartName )
{
    std::string uid;
    bool empty = false;

    if( aGeomName.empty() && aPartName.empty() )
    {
        uid = "NOGEOM_NOPART";
        empty = true;
    }
    else
    {
        uid = aGeomName + "_" + aPartName;
    }

    IDF3_COMP_OUTLINE* cp = GetComponentOutline( uid );

    if( cp != nullptr )
        return cp;

    cp = new IDF3_COMP_OUTLINE( this );

    if( cp == nullptr )
    {
        ostringstream ostr;
        ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "(): ";
        cerr << "could not create new outline";
        errormsg = ostr.str();

        return nullptr;
    }

    if( empty )
        cp->CreateDefaultOutline( "", "" );
    else
        cp->CreateDefaultOutline( aGeomName, aPartName );

    compOutlines.insert( pair<const std::string, IDF3_COMP_OUTLINE*>(cp->GetUID(), cp) );

    return cp;
}


void IDF3_BOARD::Clear( void )
{
    // preserve the board thickness
    double thickness = olnBoard.GetThickness();

    idfSource.clear();
    brdSource.clear();
    libSource.clear();
    brdDate.clear();
    libDate.clear();
    uidFileList.clear();
    uidLibList.clear();
    brdFileVersion = 0;
    libFileVersion = 0;
    iRefDes = 0;
    sRefDes.clear();

    // delete comment lists
    noteComments.clear();
    drillComments.clear();
    placeComments.clear();

    // delete notes
    while( !notes.empty() )
    {
        delete notes.front();
        notes.pop_front();
    }

    // delete drill list
    do
    {
        std::list<IDF_DRILL_DATA*>::iterator ds = board_drills.begin();
        std::list<IDF_DRILL_DATA*>::iterator de = board_drills.end();

        while( ds != de )
        {
            delete *ds;
            ++ds;
        }

        board_drills.clear();
    } while( 0 );


    // delete components
    do
    {
        std::map<std::string, IDF3_COMPONENT*>::iterator cs = components.begin();
        std::map<std::string, IDF3_COMPONENT*>::iterator ce = components.end();

        while( cs != ce )
        {
            delete cs->second;
            ++cs;
        }

        components.clear();
    } while( 0 );


    // delete component outlines
    do
    {
        std::map<std::string, IDF3_COMP_OUTLINE*>::iterator cs = compOutlines.begin();
        std::map<std::string, IDF3_COMP_OUTLINE*>::iterator ce = compOutlines.end();

        while( cs != ce )
        {
            delete cs->second;
            ++cs;
        }

        compOutlines.clear();
    } while( 0 );


    // delete OTHER outlines
    do
    {
        std::map<std::string, OTHER_OUTLINE*>::iterator os = olnOther.begin();
        std::map<std::string, OTHER_OUTLINE*>::iterator oe = olnOther.end();

        while( os != oe )
        {
            delete os->second;
            ++os;
        }

        olnOther.clear();
    } while( 0 );


    // delete ROUTE outlines
    do
    {
        std::list<ROUTE_OUTLINE*>::iterator os = olnRoute.begin();
        std::list<ROUTE_OUTLINE*>::iterator oe = olnRoute.end();

        while( os != oe )
        {
            delete *os;
            ++os;
        }

        olnRoute.clear();
    } while( 0 );


    // delete PLACE outlines
    do
    {
        std::list<PLACE_OUTLINE*>::iterator os = olnPlace.begin();
        std::list<PLACE_OUTLINE*>::iterator oe = olnPlace.end();

        while( os != oe )
        {
            delete *os;
            ++os;
        }

        olnPlace.clear();
    } while( 0 );


    // delete ROUTE KEEPOUT outlines
    do
    {
        std::list<ROUTE_KO_OUTLINE*>::iterator os = olnRouteKeepout.begin();
        std::list<ROUTE_KO_OUTLINE*>::iterator oe = olnRouteKeepout.end();

        while( os != oe )
        {
            delete *os;
            ++os;
        }

        olnRouteKeepout.clear();
    } while( 0 );


    // delete VIA KEEPOUT outlines
    do
    {
        std::list<VIA_KO_OUTLINE*>::iterator os = olnViaKeepout.begin();
        std::list<VIA_KO_OUTLINE*>::iterator oe = olnViaKeepout.end();

        while( os != oe )
        {
            delete *os;
            ++os;
        }

        olnViaKeepout.clear();
    } while( 0 );


    // delete PLACEMENT KEEPOUT outlines
    do
    {
        std::list<PLACE_KO_OUTLINE*>::iterator os = olnPlaceKeepout.begin();
        std::list<PLACE_KO_OUTLINE*>::iterator oe = olnPlaceKeepout.end();

        while( os != oe )
        {
            delete *os;
            ++os;
        }

        olnPlaceKeepout.clear();
    } while( 0 );


    // delete PLACEMENT GROUP outlines
    do
    {
        std::multimap<std::string, GROUP_OUTLINE*>::iterator os = olnGroup.begin();
        std::multimap<std::string, GROUP_OUTLINE*>::iterator oe = olnGroup.end();

        while( os != oe )
        {
            delete os->second;
            ++os;
        }

        olnGroup.clear();
    } while( 0 );

    boardName.clear();
    olnBoard.setThickness( thickness );

    unit      = UNIT_MM;
    userScale = 1.0;
    userXoff  = 0.0;
    userYoff  = 0.0;
}


const std::map<std::string, OTHER_OUTLINE*>* const IDF3_BOARD::GetOtherOutlines( void )
{
    return &olnOther;
}
