/*
 * This program source code file is part of KiCad, a free EDA CAD application.
 *
 * Copyright (C) 2020-2021 Roberto Fernandez Bautista <roberto.fer.bau@gmail.com>
 * Copyright (C) 2020-2023 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 3 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, see <http://www.gnu.org/licenses/>.
 */

/**
 * @file cadstar_sch_archive_loader.cpp
 * @brief Loads a csa file into a KiCad SCHEMATIC object
 */

#include <sch_io/cadstar/cadstar_sch_archive_loader.h>
#include <io/cadstar/cadstar_parts_lib_parser.h>

#include <bus_alias.h>
#include <core/mirror.h>
#include <core/kicad_algo.h>
#include <eda_text.h>
#include <lib_shape.h>
#include <lib_text.h>
#include <macros.h>
#include <progress_reporter.h>
#include <string_utils.h>
#include <sch_bus_entry.h>
#include <sch_edit_frame.h> //SYMBOL_ORIENTATION_T
#include <sch_io/sch_io_mgr.h>
#include <sch_junction.h>
#include <sch_line.h>
#include <sch_screen.h>
#include <sch_shape.h>
#include <sch_sheet.h>
#include <sch_sheet_path.h>
#include <sch_sheet_pin.h>
#include <sch_label.h>
#include <schematic.h>
#include <sim/sim_model.h>
#include <trigo.h>
#include <wildcards_and_files_ext.h>


const wxString PartNameFieldName = "Part Name";
const wxString PartNumberFieldName = "Part Number";
const wxString PartVersionFieldName = "Part Version";
const wxString PartAcceptanceFieldName = "Part Acceptance";


std::vector<LIB_SYMBOL*> CADSTAR_SCH_ARCHIVE_LOADER::LoadPartsLib( const wxString& aFilename )
{
    if( m_progressReporter )
        m_progressReporter->SetNumPhases( 3 ); // (0) Read csa, (1) Parse csa, (3) Load lib

    Parse();

    CADSTAR_PARTS_LIB_PARSER p;

    if( !p.CheckFileHeader( aFilename.utf8_string() ) )
        THROW_IO_ERROR(
                _( "The selected file does not appear to be a CADSTAR parts Library file" ) );

    // TODO: we could add progress reporting for reading .lib
    CADSTAR_PARTS_LIB_MODEL csLib = p.ReadFile( aFilename.utf8_string() );

    if( m_progressReporter )
    {
        m_progressReporter->BeginPhase( 2 );
        long numSteps = csLib.m_PartEntries.size();
        m_progressReporter->SetMaxProgress( numSteps );
    }

    std::vector<LIB_SYMBOL*> retVal;

    for( const CADSTAR_PART_ENTRY& part : csLib.m_PartEntries )
    {
        std::unique_ptr<LIB_SYMBOL> loadedPart = loadLibPart( part );

        checkPoint();

        if( loadedPart )
            retVal.push_back( loadedPart.release() );
    }

    return retVal;
}


std::unique_ptr<LIB_SYMBOL>
CADSTAR_SCH_ARCHIVE_LOADER::loadLibPart( const CADSTAR_PART_ENTRY& aPart )
{
    wxString                    escapedPartName = EscapeString( aPart.m_Name, CTX_LIBID );
    std::unique_ptr<LIB_SYMBOL> retSym;

    int unit = 0;

    for( const CADSTAR_PART_SYMBOL_ENTRY& sym : aPart.m_Symbols )
    {
        ++unit;
        wxString  alternateName = sym.m_SymbolAlternateName.value_or( "" );
        SYMDEF_ID symbolID = getSymDefFromName( sym.m_SymbolName, alternateName );

        if( !Library.SymbolDefinitions.count( symbolID ) )
        {
            m_reporter->Report(
                    wxString::Format( _( "Unable to find symbol %s, referenced by part %s. The "
                                         "part was not loaded." ),
                                      generateLibName( sym.m_SymbolName, alternateName ),
                                      aPart.m_Name ),
                    RPT_SEVERITY_ERROR );

            return nullptr;
        }

        // Load the graphical symbol for this gate
        std::unique_ptr<LIB_SYMBOL> kiSymDef( loadSymdef( symbolID )->Duplicate() );

        if( (int)sym.m_Pins.size() != kiSymDef->GetPinCount() )
        {
            m_reporter->Report(
                    wxString::Format( _( "Inconsistent pin numbers in symbol %s compared to the one "
                                         "defined in part %s. The part was not loaded." ),
                                      generateLibName( sym.m_SymbolName, alternateName ),
                                      aPart.m_Name ),
                    RPT_SEVERITY_ERROR );

            return nullptr;
        }

        wxASSERT( m_symDefTerminalsMap.count( symbolID ) ); //loadSymDef should have populated this


        // Update the pin numbers to match those defined in the Cadstar part
        for( auto& [storedPinNum, termID] : m_symDefTerminalsMap[symbolID] )
        {
            wxCHECK( termID > 0 && sym.m_Pins.size() >= size_t( termID ), nullptr );
            LIB_PIN* pin = kiSymDef->GetPin( storedPinNum );
            size_t   termIdx = size_t( termID ) - 1;

            // For now leave numerical pin number. Otherwise, when loading the
            // .cpa file we won't be able to link up to the footprint pads, but if
            // we solve this, we could then load alphanumeric pin numbers as below:
            //
            // if( aPart.m_PinNamesMap.count( termID ) )
            //  partPinNum = wxString( aPart.m_PinNamesMap.at( termID ) );
            //
            wxString partPinNum = wxString::Format( "%ld", sym.m_Pins[termIdx].m_Identifier );
            pin->SetNumber( partPinNum );

            if( aPart.m_PinNamesMap.count( termID ) )
                pin->SetName( HandleTextOverbar( aPart.m_PinNamesMap.at( termID ) ) );
            else if( aPart.m_PinLabelsMap.count( termID ) )
                pin->SetName( HandleTextOverbar( aPart.m_PinLabelsMap.at( termID ) ) );

            pin->SetType( getKiCadPinType( sym.m_Pins[termIdx].m_Type ) );

            // @todo: Load pin/gate swapping information once kicad supports this
        }

        if( unit == 1 )
        {
            wxCHECK( kiSymDef->GetUnitCount() == 1, nullptr );
            // The first unit can just be moved to the part symbol
            retSym = std::move( kiSymDef );

            retSym->SetUnitCount( aPart.m_Symbols.size(), true );

            retSym->SetName( escapedPartName );
            retSym->GetReferenceField().SetText( aPart.m_ComponentStem );
            retSym->GetValueField().SetText( aPart.m_Value.value_or( "" ) );
            addNewFieldToSymbol( PartNameFieldName, retSym )->SetText( aPart.m_Name );
            retSym->SetDescription( aPart.m_Description.value_or( "" ) );

            auto addFieldIfHasValue =
                [&]( const wxString& aFieldName, const std::optional<std::string>& aFieldValue )
                {
                    if( aFieldValue.has_value() )
                        addNewFieldToSymbol( aFieldName, retSym )->SetText( aFieldValue.value() );
                };

            addFieldIfHasValue( PartNumberFieldName,        aPart.m_Number );
            addFieldIfHasValue( PartVersionFieldName,       aPart.m_Version );
            addFieldIfHasValue( PartAcceptanceFieldName,    aPart.m_AcceptancePartName );

            setFootprintOnSymbol( retSym, aPart.m_Pcb_component,
                                  aPart.m_Pcb_alternate.value_or( "" ) );

            if(aPart.m_SpiceModel.has_value())
            {
                wxString modelVal = wxString::Format( "model=\"%s\"", aPart.m_SpiceModel.value() );
                addNewFieldToSymbol( SIM_DEVICE_FIELD, retSym )->SetText( "SPICE" );
                addNewFieldToSymbol( SIM_PARAMS_FIELD, retSym )->SetText( modelVal );
            }

            // Load all part attributes, regardless of original cadstar type, to the symbol

            // @todo some cadstar part attributes have a "read-only" flag. We should load this
            // when KiCad supports read-only fields.

            for( auto& [fieldName, value] : aPart.m_UserAttributes )
                addNewFieldToSymbol( fieldName, retSym )->SetText( value );

            for( auto& [fieldName, attrValue] : aPart.m_SchAttributes )
                addNewFieldToSymbol( fieldName, retSym )->SetText( attrValue.m_Value );

            for( auto& [fieldName, attrValue] : aPart.m_PcbAttributes )
                addNewFieldToSymbol( fieldName, retSym )->SetText( attrValue.m_Value );

            for( auto& [fieldName, attrValue] : aPart.m_SchAndPcbAttributes )
                addNewFieldToSymbol( fieldName, retSym )->SetText( attrValue.m_Value );

            for( auto& [fieldName, attrValue] : aPart.m_PartAttributes )
                addNewFieldToSymbol( fieldName, retSym )->SetText( attrValue.m_Value );

            // Load all hidden pins onto the first unit of the symbol in KiCad
            // We load them in a spiral sequence, starting at the center of the symbol BBOX
            VECTOR2I symCenter = retSym->GetBodyBoundingBox( unit, 0, false, false ).GetCenter();
            symCenter.y = -symCenter.y; // need to invert the y coord for lib symbols.

            VECTOR2I delta( 0, 1 );
            VECTOR2I direction( 0, -1 );
            int spacing = schIUScale.MilsToIU( 50 ); // for now, place on a 50mil grid

            for( auto& [signalName, csPinVector] : aPart.m_HiddenPins )
            {
                for(  const CADSTAR_PART_PIN& csPin : csPinVector )
                {
                    std::unique_ptr<LIB_PIN> pin = std::make_unique<LIB_PIN>( retSym.get() );

                    long pinNum = csPin.m_Identifier;
                    pin->SetNumber( wxString::Format( "%ld", pinNum ) );
                    pin->SetName( signalName );
                    pin->SetType( ELECTRICAL_PINTYPE::PT_POWER_IN );

                    pin->SetVisible( false );

                    // Generate the coordinate for the pin. We don't want overlapping pins
                    // and ideally close to the center of the symbol, so we load pins
                    // in a spiral sequence around the center
                    if( delta.x == delta.y || ( delta.x < 0 && delta.x == -delta.y )
                        || ( delta.x > 0 && delta.x == 1 - delta.y ) )
                    {
                        // change direction
                        direction = { -direction.y, direction.x };
                    }

                    delta += direction;
                    VECTOR2I offset = delta * spacing;
                    pin->SetPosition( symCenter + offset );
                    pin->SetLength( 0 ); //CADSTAR Pins are just a point (have no length)
                    pin->SetShape( GRAPHIC_PINSHAPE::LINE );
                    pin->SetUnit( unit );
                    retSym->AddDrawItem( pin.release() );
                }
            }
        }
        else
        {                 // Source:   Dest:
            copySymbolItems( kiSymDef, retSym, unit, false /* aOverrideFields */ );
        }


        retSym->SetShowPinNames( aPart.m_PinsVisible );
        retSym->SetShowPinNumbers( aPart.m_PinsVisible );
    }


    return retSym;
}


void CADSTAR_SCH_ARCHIVE_LOADER::copySymbolItems( std::unique_ptr<LIB_SYMBOL>& aSourceSym,
                                                  std::unique_ptr<LIB_SYMBOL>& aDestSym,
                                                  int aDestUnit, bool aOverrideFields )
{
    // Ensure there are no items on the unit we want to load onto
    for( LIB_ITEM* item : aDestSym->GetUnitDrawItems( aDestUnit, 0 /*aConvert*/ ) )
        aDestSym->RemoveDrawItem( item );

    // Copy all draw items
    for( LIB_ITEM* newItem : aSourceSym->GetUnitDrawItems( 1, 0 /*aConvert*/ ) )
    {
        LIB_ITEM* itemCopy = static_cast<LIB_ITEM*>( newItem->Clone() );
        itemCopy->SetParent( aDestSym.get() );
        itemCopy->SetUnit( aDestUnit );
        aDestSym->AddDrawItem( itemCopy );
    }

    //Copy / override all fields
    if( aOverrideFields )
    {
        std::vector<LIB_FIELD*> fieldsToCopy;
        aSourceSym->GetFields( fieldsToCopy );

        for( LIB_FIELD* templateField : fieldsToCopy )
        {
            LIB_FIELD* appliedField = addNewFieldToSymbol( templateField->GetName(), aDestSym );
            templateField->Copy( appliedField );
        }
    }
}


void CADSTAR_SCH_ARCHIVE_LOADER::Load( SCHEMATIC* aSchematic, SCH_SHEET* aRootSheet )
{
    wxCHECK( aSchematic, /* void */ );

    if( m_progressReporter )
        m_progressReporter->SetNumPhases( 3 ); // (0) Read file, (1) Parse file, (2) Load file

    Parse();

    checkDesignLimits(); // Throws if error found

    // Assume the center at 0,0 since we are going to be translating the design afterwards anyway
    m_designCenter = { 0, 0 };

    m_schematic = aSchematic;
    m_rootSheet = aRootSheet;

    if( m_progressReporter )
    {
        m_progressReporter->BeginPhase( 2 );
        long numSteps = 11; // one step for each of below functions + one at the end of import

        // Step 4 is by far the longest - add granularity in reporting
        numSteps += Parts.PartDefinitions.size();

        m_progressReporter->SetMaxProgress( numSteps );
    }

    loadTextVariables(); // Load text variables right at the start to ensure bounding box
                         // calculations work correctly for text items
    checkPoint(); // Step 1
    loadSheets();
    checkPoint(); // Step 2
    loadHierarchicalSheetPins();
    checkPoint(); // Step 3
    loadPartsLibrary();
    checkPoint(); // Step 4, Subdivided into extra steps
    loadSchematicSymbolInstances();
    checkPoint(); // Step 5
    loadBusses();
    checkPoint(); // Step 6
    loadNets();
    checkPoint(); // Step 7
    loadFigures();
    checkPoint(); // Step 8
    loadTexts();
    checkPoint(); // Step 9
    loadDocumentationSymbols();
    checkPoint(); // Step 10

    if( Schematic.VariantHierarchy.Variants.size() > 0 )
    {
        m_reporter->Report( wxString::Format( _( "The CADSTAR design contains variants which has "
                                                 "no KiCad equivalent. Only the master variant "
                                                 "('%s') was loaded." ),
                                              Schematic.VariantHierarchy.Variants.at( "V0" ).Name ),
                            RPT_SEVERITY_WARNING );
    }

    if( Schematic.Groups.size() > 0 )
    {
        m_reporter->Report( _( "The CADSTAR design contains grouped items which has no KiCad "
                               "equivalent. Any grouped items have been ungrouped." ),
                            RPT_SEVERITY_WARNING );
    }

    if( Schematic.ReuseBlocks.size() > 0 )
    {
        m_reporter->Report( _( "The CADSTAR design contains re-use blocks which has no KiCad "
                               "equivalent. The re-use block information has been discarded during "
                               "the import." ),
                            RPT_SEVERITY_WARNING );
    }


    // For all sheets, center all elements and re calculate the page size:
    for( std::pair<LAYER_ID, SCH_SHEET*> sheetPair : m_sheetMap )
    {
        SCH_SHEET* sheet = sheetPair.second;

        // Calculate the new sheet size.
        BOX2I sheetBoundingBox;

        for( SCH_ITEM* item : sheet->GetScreen()->Items() )
        {
            BOX2I bbox;

            // Only use the visible fields of the symbols to calculate their bounding box
            // (hidden fields could be very long and artificially enlarge the sheet bounding box)
            if( item->Type() == SCH_SYMBOL_T )
            {
                SCH_SYMBOL* comp = static_cast<SCH_SYMBOL*>( item );
                bbox = comp->GetBodyAndPinsBoundingBox();

                for( const SCH_FIELD& field : comp->GetFields() )
                {
                    if( field.IsVisible() )
                        bbox.Merge( field.GetBoundingBox() );
                }
            }
            else if( item->Type() == SCH_TEXT_T )
            {
                SCH_TEXT* txtItem = static_cast<SCH_TEXT*>( item );
                wxString  txt = txtItem->GetText();

                if( txt.Contains( "${" ) )
                    continue; // We can't calculate bounding box of text items with variables
                else
                    bbox = txtItem->GetBoundingBox();
            }
            else
            {
                bbox = item->GetBoundingBox();
            }

            sheetBoundingBox.Merge( bbox );
        }

        // Find the screen grid of the original CADSTAR design
        int grid = Assignments.Grids.ScreenGrid.Param1;

        if( Assignments.Grids.ScreenGrid.Type == GRID_TYPE::FRACTIONALGRID )
            grid = grid / Assignments.Grids.ScreenGrid.Param2;
        else if( Assignments.Grids.ScreenGrid.Param2 > grid )
            grid = Assignments.Grids.ScreenGrid.Param2;

        grid = getKiCadLength( grid );

        auto roundToNearestGrid =
                [&]( int aNumber ) -> int
                {
                    int error = aNumber % grid;
                    int absError = sign( error ) * error;

                    if( absError > ( grid / 2 ) )
                        return aNumber + ( sign( error ) * grid ) - error;
                    else
                        return aNumber - error;
                };

        // When exporting to pdf, CADSTAR applies a margin of 3% of the longest dimension (height
        // or width) to all 4 sides (top, bottom, left right). For the import, we are also rounding
        // the margin to the nearest grid, ensuring all items remain on the grid.
        VECTOR2I targetSheetSize = sheetBoundingBox.GetSize();
        int    longestSide = std::max( targetSheetSize.x, targetSheetSize.y );
        int    margin = ( (double) longestSide * 0.03 );
        margin = roundToNearestGrid( margin );
        targetSheetSize += margin * 2;

        // Update page size always
        PAGE_INFO pageInfo = sheet->GetScreen()->GetPageSettings();
        pageInfo.SetWidthMils( schIUScale.IUToMils( targetSheetSize.x ) );
        pageInfo.SetHeightMils( schIUScale.IUToMils( targetSheetSize.y ) );

        // Set the new sheet size.
        sheet->GetScreen()->SetPageSettings( pageInfo );

        VECTOR2I pageSizeIU = sheet->GetScreen()->GetPageSettings().GetSizeIU( schIUScale.IU_PER_MILS );
        VECTOR2I sheetcentre( pageSizeIU.x / 2, pageSizeIU.y / 2 );
        VECTOR2I itemsCentre = sheetBoundingBox.Centre();

        // round the translation to nearest point on the grid
        VECTOR2I translation = sheetcentre - itemsCentre;
        translation.x = roundToNearestGrid( translation.x );
        translation.y = roundToNearestGrid( translation.y );

        // Translate the items.
        std::vector<SCH_ITEM*> allItems;

        std::copy( sheet->GetScreen()->Items().begin(), sheet->GetScreen()->Items().end(),
                   std::back_inserter( allItems ) );

        for( SCH_ITEM* item : allItems )
        {
            item->Move( translation );
            item->ClearFlags();
            sheet->GetScreen()->Update( item );
        }
    }

    checkPoint();

    m_reporter->Report( _( "CADSTAR fonts are different to the ones in KiCad. This will likely "
                           "result in alignment issues. Please review the imported text elements "
                           "carefully and correct manually if required." ),
                        RPT_SEVERITY_WARNING );

    m_reporter->Report( _( "The CADSTAR design has been imported successfully.\n"
                           "Please review the import errors and warnings (if any)." ) );
}


void CADSTAR_SCH_ARCHIVE_LOADER::checkDesignLimits()
{
    LONGPOINT designLimit = Assignments.Settings.DesignLimit;

    //Note: can't use getKiCadPoint() due VECTOR2I being int - need long long to make the check
    long long designSizeXkicad = (long long) designLimit.x / KiCadUnitDivider;
    long long designSizeYkicad = (long long) designLimit.y / KiCadUnitDivider;

    // Max size limited by the positive dimension of VECTOR2I (which is an int)
    constexpr long long maxDesignSizekicad = std::numeric_limits<int>::max();

    if( designSizeXkicad > maxDesignSizekicad || designSizeYkicad > maxDesignSizekicad )
    {
        THROW_IO_ERROR( wxString::Format(
                _( "The design is too large and cannot be imported into KiCad. \n"
                   "Please reduce the maximum design size in CADSTAR by navigating to: \n"
                   "Design Tab -> Properties -> Design Options -> Maximum Design Size. \n"
                   "Current Design size: %.2f, %.2f millimeters. \n"
                   "Maximum permitted design size: %.2f, %.2f millimeters.\n" ),
                (double) designSizeXkicad / SCH_IU_PER_MM,
                (double) designSizeYkicad / SCH_IU_PER_MM,
                (double) maxDesignSizekicad / SCH_IU_PER_MM,
                (double) maxDesignSizekicad / SCH_IU_PER_MM ) );
    }
}


void CADSTAR_SCH_ARCHIVE_LOADER::loadSheets()
{
    const std::vector<LAYER_ID>& orphanSheets = findOrphanSheets();
    SCH_SHEET_PATH               rootPath;
    rootPath.push_back( m_rootSheet );
    rootPath.SetPageNumber( wxT( "1" ) );

    if( orphanSheets.size() > 1 )
    {
        int x = 1;
        int y = 1;

        for( LAYER_ID sheetID : orphanSheets )
        {
            VECTOR2I pos( x * schIUScale.MilsToIU( 1000 ), y * schIUScale.MilsToIU( 1000 ) );
            VECTOR2I siz( schIUScale.MilsToIU( 1000 ), schIUScale.MilsToIU( 1000 ) );

            loadSheetAndChildSheets( sheetID, pos, siz, rootPath );

            x += 2;

            if( x > 10 ) // start next row
            {
                x = 1;
                y += 2;
            }
        }
    }
    else if( orphanSheets.size() > 0 )
    {
        LAYER_ID rootSheetID = orphanSheets.at( 0 );

        wxFileName loadedFilePath = wxFileName( Filename );

        std::string filename = wxString::Format( "%s_%02d", loadedFilePath.GetName(),
                                                 getSheetNumber( rootSheetID ) )
                                       .ToStdString();
        ReplaceIllegalFileNameChars( &filename );
        filename += wxT( "." ) + FILEEXT::KiCadSchematicFileExtension;

        wxFileName fn( m_schematic->Prj().GetProjectPath() + filename );
        m_rootSheet->GetScreen()->SetFileName( fn.GetFullPath() );

        m_sheetMap.insert( { rootSheetID, m_rootSheet } );
        loadChildSheets( rootSheetID, rootPath );
    }
    else if( Header.Format.Type == "SYMBOL" )
    {
        THROW_IO_ERROR( _( "The selected file is a CADSTAR symbol library. It does not contain a "
                           "schematic design so cannot be imported/opened in this way." ) );
    }
    else
    {
        THROW_IO_ERROR( _( "The CADSTAR schematic might be corrupt: there is no root sheet." ) );
    }
}


void CADSTAR_SCH_ARCHIVE_LOADER::loadHierarchicalSheetPins()
{
    for( std::pair<BLOCK_ID, BLOCK> blockPair : Schematic.Blocks )
    {
        BLOCK&   block   = blockPair.second;
        LAYER_ID sheetID = "";

        if( block.Type == BLOCK::TYPE::PARENT )
            sheetID = block.LayerID;
        else if( block.Type == BLOCK::TYPE::CHILD )
            sheetID = block.AssocLayerID;
        else
            continue;

        if( m_sheetMap.find( sheetID ) != m_sheetMap.end() )
        {
            SCH_SHEET* sheet = m_sheetMap.at( sheetID );

            for( std::pair<TERMINAL_ID, TERMINAL> termPair : block.Terminals )
            {
                TERMINAL term = termPair.second;
                wxString name = "YOU SHOULDN'T SEE THIS TEXT. THIS IS A BUG.";

                SCH_HIERLABEL* sheetPin = nullptr;

                if( block.Type == BLOCK::TYPE::PARENT )
                    sheetPin = new SCH_HIERLABEL();
                else if( block.Type == BLOCK::TYPE::CHILD )
                    sheetPin = new SCH_SHEET_PIN( sheet );

                sheetPin->SetText( name );
                sheetPin->SetShape( LABEL_FLAG_SHAPE::L_UNSPECIFIED );
                sheetPin->SetSpinStyle( getSpinStyle( term.OrientAngle, false ) );
                sheetPin->SetPosition( getKiCadPoint( term.Position ) );

                if( sheetPin->Type() == SCH_SHEET_PIN_T )
                    sheet->AddPin( (SCH_SHEET_PIN*) sheetPin );
                else
                    sheet->GetScreen()->Append( sheetPin );

                BLOCK_PIN_ID blockPinID = std::make_pair( block.ID, term.ID );
                m_sheetPinMap.insert( { blockPinID, sheetPin } );
            }
        }
    }
}


void CADSTAR_SCH_ARCHIVE_LOADER::loadPartsLibrary()
{
    for( std::pair<PART_ID, PART> partPair : Parts.PartDefinitions )
    {
        PART_ID partID = partPair.first;
        PART    part = partPair.second;

        wxString    escapedPartName = EscapeString( part.Name, CTX_LIBID );
        LIB_SYMBOL* kiSym = new LIB_SYMBOL( escapedPartName );

        kiSym->SetUnitCount( part.Definition.GateSymbols.size() );
        bool ok = true;

        for( std::pair<GATE_ID, PART::DEFINITION::GATE> gatePair : part.Definition.GateSymbols )
        {
            GATE_ID                gateID   = gatePair.first;
            PART::DEFINITION::GATE gate     = gatePair.second;
            SYMDEF_ID              symbolID = getSymDefFromName( gate.Name, gate.Alternate );

            if( symbolID.IsEmpty() )
            {
                m_reporter->Report( wxString::Format( _( "Part definition '%s' references symbol "
                                                         "'%s' (alternate '%s') which could not be "
                                                         "found in the symbol library. The part has "
                                                         "not been loaded into the KiCad library." ),
                                                      part.Name,
                                                      gate.Name,
                                                      gate.Alternate ),
                                    RPT_SEVERITY_WARNING);

                ok = false;
                break;
            }

            m_partSymbolsMap.insert( { { partID, gateID }, symbolID } );
            loadSymbolGateAndPartFields( symbolID, part, gateID, kiSym );
        }

        if( ok && part.Definition.GateSymbols.size() != 0 )
        {
            m_loadedSymbols.push_back( kiSym );
        }
        else
        {
            if( part.Definition.GateSymbols.size() == 0 )
            {
                m_reporter->Report(
                        wxString::Format( _( "Part definition '%s' has an incomplete definition (no"
                                             " symbol definitions are associated with it). The part"
                                             " has not been loaded into the KiCad library." ),
                                          part.Name ),
                        RPT_SEVERITY_WARNING );
            }

            // Don't save in the library, but still keep it cached as some of the units might have
            // been loaded correctly (saving us time later on), plus the part definition contains
            // the part name, which is important to load
        }

        m_partMap.insert( { partID, kiSym } );

        checkPoint();
    }
}


void CADSTAR_SCH_ARCHIVE_LOADER::loadSchematicSymbolInstances()
{
    for( std::pair<SYMBOL_ID, SYMBOL> symPair : Schematic.Symbols )
    {
        SYMBOL sym = symPair.second;

        if( !sym.VariantID.empty() && sym.VariantParentSymbolID != sym.ID )
            continue; // Only load master Variant

        if( sym.IsComponent )
        {
            if( m_partMap.find( sym.PartRef.RefID ) == m_partMap.end() )
            {
                m_reporter->Report( wxString::Format( _( "Symbol '%s' references part '%s' which "
                                                         "could not be found in the library. The "
                                                         "symbol was not loaded" ),
                                                      sym.ComponentRef.Designator,
                                                      sym.PartRef.RefID ),
                                    RPT_SEVERITY_ERROR );

                continue;
            }

            if( sym.GateID.IsEmpty() )
                sym.GateID = wxT( "A" ); // Assume Gate "A" if unspecified

            PART_GATE_ID partSymbolID = { sym.PartRef.RefID, sym.GateID };
            LIB_SYMBOL*  kiSym = m_partMap.at( sym.PartRef.RefID );
            bool         copy = false;

            // The symbol definition in the part either does not exist for this gate number
            // or is different to the symbol instance. We need to reload the gate for this
            // symbol
            if( m_partSymbolsMap.find( partSymbolID ) == m_partSymbolsMap.end()
                || m_partSymbolsMap.at( partSymbolID ) != sym.SymdefID )
            {
                kiSym = new LIB_SYMBOL( *kiSym ); // Make a copy
                copy = true;
                const PART& part = Parts.PartDefinitions.at( sym.PartRef.RefID );
                loadSymbolGateAndPartFields( sym.SymdefID, part, sym.GateID, kiSym );
            }

            LIB_SYMBOL* scaledPart = getScaledLibPart( kiSym, sym.ScaleRatioNumerator,
                                                       sym.ScaleRatioDenominator );

            EDA_ANGLE   symOrient = ANGLE_0;
            SCH_SYMBOL* symbol = loadSchematicSymbol( sym, *scaledPart, symOrient );

            delete scaledPart;

            if( copy )
                delete kiSym;

            SCH_FIELD* refField = symbol->GetField( REFERENCE_FIELD );

            sym.ComponentRef.Designator.Replace( wxT( "\n" ), wxT( "\\n" ) );
            sym.ComponentRef.Designator.Replace( wxT( "\r" ), wxT( "\\r" ) );
            sym.ComponentRef.Designator.Replace( wxT( "\t" ), wxT( "\\t" ) );
            sym.ComponentRef.Designator.Replace( wxT( " " ), wxT( "_" ) );

            refField->SetText( sym.ComponentRef.Designator );
            loadSymbolFieldAttribute( sym.ComponentRef.AttrLoc, symOrient, sym.Mirror, refField );

            if( sym.HasPartRef )
            {
                SCH_FIELD* partField = symbol->FindField( PartNameFieldName );

                if( !partField )
                {
                    int fieldID = symbol->GetFieldCount();
                    partField = symbol->AddField( SCH_FIELD( VECTOR2I(), fieldID, symbol,
                                                             PartNameFieldName ) );
                }

                wxASSERT( partField->GetName() == PartNameFieldName );

                wxString partname = getPart( sym.PartRef.RefID ).Name;
                partname.Replace( wxT( "\n" ), wxT( "\\n" ) );
                partname.Replace( wxT( "\r" ), wxT( "\\r" ) );
                partname.Replace( wxT( "\t" ), wxT( "\\t" ) );
                partField->SetText( partname );

                loadSymbolFieldAttribute( sym.PartRef.AttrLoc, symOrient, sym.Mirror, partField );

                partField->SetVisible( SymbolPartNameColor.IsVisible );
            }

            for( auto& attr : sym.AttributeValues )
            {
                ATTRIBUTE_VALUE attrVal = attr.second;

                if( attrVal.HasLocation )
                {
                    wxString attrName = getAttributeName( attrVal.AttributeID );
                    SCH_FIELD* attrField = symbol->FindField( attrName );

                    if( !attrField )
                    {
                        int fieldID = symbol->GetFieldCount();
                        attrField = symbol->AddField(
                                SCH_FIELD( VECTOR2I(), fieldID, symbol, attrName ) );
                    }

                    wxASSERT( attrField->GetName() == attrName );

                    attrVal.Value.Replace( wxT( "\n" ), wxT( "\\n" ) );
                    attrVal.Value.Replace( wxT( "\r" ), wxT( "\\r" ) );
                    attrVal.Value.Replace( wxT( "\t" ), wxT( "\\t" ) );
                    attrField->SetText( attrVal.Value );

                    loadSymbolFieldAttribute( attrVal.AttributeLocation, symOrient, sym.Mirror,
                                              attrField );
                    attrField->SetVisible( isAttributeVisible( attrVal.AttributeID ) );
                }
            }
        }
        else if( sym.IsSymbolVariant )
        {
            if( Library.SymbolDefinitions.find( sym.SymdefID ) == Library.SymbolDefinitions.end() )
            {
                THROW_IO_ERROR( wxString::Format(
                        _( "Symbol ID '%s' references library symbol '%s' which could not be "
                           "found in the library. Did you export all items of the design?" ),
                        sym.ID, sym.PartRef.RefID ) );
            }

            SYMDEF_SCM libSymDef = Library.SymbolDefinitions.at( sym.SymdefID );

            if( libSymDef.Terminals.size() != 1 )
            {
                THROW_IO_ERROR( wxString::Format(
                        _( "Symbol ID '%s' is a signal reference or global signal but it has too "
                           "many pins. The expected number of pins is 1 but %d were found." ),
                        sym.ID, libSymDef.Terminals.size() ) );
            }

            if( sym.SymbolVariant.Type == SYMBOLVARIANT::TYPE::GLOBALSIGNAL )
            {
                SYMDEF_ID symID  = sym.SymdefID;
                LIB_SYMBOL* kiPart = nullptr;

                // In CADSTAR "GlobalSignal" is a special type of symbol which defines
                // a Power Symbol. The "Alternate" name defines the default net name of
                // the power symbol but this can be overridden in the design itself.
                wxString libraryNetName = Library.SymbolDefinitions.at( symID ).Alternate;

                // Name of the net that the symbol instance in CADSTAR refers to:
                wxString symbolInstanceNetName = sym.SymbolVariant.Reference;
                symbolInstanceNetName = EscapeString( symbolInstanceNetName, CTX_LIBID );

                // Name of the symbol we will use for saving the part in KiCad
                // Note: In CADSTAR all power symbols will start have the reference name be
                // "GLOBALSIGNAL" followed by the default net name, so it makes sense to save
                // the symbol in KiCad as the default net name as well.
                wxString libPartName = libraryNetName;

                // In CADSTAR power symbol instances can refer to a different net to that defined
                // in the library. This causes problems in KiCad v6 as it breaks connectivity when
                // the user decides to update all symbols from library. We handle this by creating
                // individual versions of the power symbol for each net name.
                if( libPartName != symbolInstanceNetName )
                {
                    libPartName += wxT( " (" ) + symbolInstanceNetName + wxT( ")" );
                }

                if( m_powerSymLibMap.find( libPartName ) == m_powerSymLibMap.end() )
                {
                    const LIB_SYMBOL* templatePart = loadSymdef( symID );
                    wxCHECK( templatePart, /*void*/ );

                    kiPart = new LIB_SYMBOL( *templatePart );
                    kiPart->SetPower();
                    kiPart->SetName( libPartName );
                    kiPart->GetValueField().SetText( symbolInstanceNetName );
                    kiPart->SetShowPinNames( false );
                    kiPart->SetShowPinNumbers( false );

                    LIB_PINS pins = kiPart->GetAllLibPins();
                    wxCHECK( pins.size() == 1, /*void*/ );

                    pins.at( 0 )->SetType( ELECTRICAL_PINTYPE::PT_POWER_IN );
                    pins.at( 0 )->SetName( symbolInstanceNetName );

                    if( libSymDef.TextLocations.find( SIGNALNAME_ORIGIN_ATTRID )
                        != libSymDef.TextLocations.end() )
                    {
                        TEXT_LOCATION& txtLoc =
                                libSymDef.TextLocations.at( SIGNALNAME_ORIGIN_ATTRID );

                        VECTOR2I valPos = getKiCadLibraryPoint( txtLoc.Position, libSymDef.Origin );

                        kiPart->GetValueField().SetPosition( valPos );
                        kiPart->GetValueField().SetVisible( true );
                    }
                    else
                    {
                        kiPart->GetValueField().SetVisible( false );
                    }

                    kiPart->GetReferenceField().SetText( "#PWR" );
                    kiPart->GetReferenceField().SetVisible( false );
                    m_loadedSymbols.push_back( kiPart );
                    m_powerSymLibMap.insert( { libPartName, kiPart } );
                }
                else
                {
                    kiPart = m_powerSymLibMap.at( libPartName );
                    wxASSERT( kiPart->GetValueField().GetText() == symbolInstanceNetName );
                }

                LIB_SYMBOL* scaledPart = getScaledLibPart( kiPart, sym.ScaleRatioNumerator,
                                                           sym.ScaleRatioDenominator );

                EDA_ANGLE   returnedOrient = ANGLE_0;
                SCH_SYMBOL* symbol = loadSchematicSymbol( sym, *scaledPart, returnedOrient );
                m_powerSymMap.insert( { sym.ID, symbol } );

                delete scaledPart;
            }
            else if( sym.SymbolVariant.Type == SYMBOLVARIANT::TYPE::SIGNALREF )
            {
                // There should only be one pin and we'll use that to set the position
                TERMINAL& symbolTerminal = libSymDef.Terminals.begin()->second;
                VECTOR2I  terminalPosOffset = symbolTerminal.Position - libSymDef.Origin;
                EDA_ANGLE rotate = getAngle( sym.OrientAngle );

                if( sym.Mirror )
                    rotate += ANGLE_180;

                RotatePoint( terminalPosOffset, -rotate );

                SCH_GLOBALLABEL* netLabel = new SCH_GLOBALLABEL;
                netLabel->SetPosition( getKiCadPoint( (VECTOR2I)sym.Origin + terminalPosOffset ) );
                netLabel->SetText( "***UNKNOWN NET****" ); // This should be later updated when we load the netlist
                netLabel->SetTextSize( VECTOR2I( schIUScale.MilsToIU( 50 ), schIUScale.MilsToIU( 50 ) ) );

                SYMDEF_SCM symbolDef = Library.SymbolDefinitions.at( sym.SymdefID );

                if( symbolDef.TextLocations.count( LINK_ORIGIN_ATTRID ) )
                {
                    TEXT_LOCATION linkOrigin = symbolDef.TextLocations.at( LINK_ORIGIN_ATTRID );
                    applyTextSettings( netLabel, linkOrigin.TextCodeID, linkOrigin.Alignment,
                                       linkOrigin.Justification );
                }

                netLabel->SetSpinStyle( getSpinStyle( sym.OrientAngle, sym.Mirror ) );

                if( libSymDef.Alternate.Lower().Contains( "in" ) )
                    netLabel->SetShape( LABEL_FLAG_SHAPE::L_INPUT );
                else if( libSymDef.Alternate.Lower().Contains( "bi" ) )
                    netLabel->SetShape( LABEL_FLAG_SHAPE::L_BIDI );
                else if( libSymDef.Alternate.Lower().Contains( "out" ) )
                    netLabel->SetShape( LABEL_FLAG_SHAPE::L_OUTPUT );
                else
                    netLabel->SetShape( LABEL_FLAG_SHAPE::L_UNSPECIFIED );

                SCH_SCREEN* screen = m_sheetMap.at( sym.LayerID )->GetScreen();

                // autoplace intersheet refs
                netLabel->AutoplaceFields( screen, false );

                screen->Append( netLabel );
                m_globalLabelsMap.insert( { sym.ID, netLabel } );
            }
            else
            {
                wxASSERT_MSG( false, "Unknown Symbol Variant." );
            }
        }
        else
        {
            m_reporter->Report( wxString::Format( _( "Symbol ID '%s' is of an unknown type. It is "
                                                     "neither a symbol or a net power / symbol. "
                                                     "The symbol was not loaded." ),
                                                  sym.ID ),
                                RPT_SEVERITY_ERROR );
        }

        if( sym.ScaleRatioDenominator != 1 || sym.ScaleRatioNumerator != 1 )
        {
            wxString symbolName = sym.ComponentRef.Designator;

            if( symbolName.empty() )
                symbolName = wxString::Format( "ID: %s", sym.ID );
            else
                symbolName += sym.GateID;

            m_reporter->Report( wxString::Format( _( "Symbol '%s' is scaled in the original "
                                                     "CADSTAR schematic but this is not supported "
                                                     "in KiCad. When the symbol is reloaded from "
                                                     "the library, it will revert to the original "
                                                     "1:1 scale." ),
                                                  symbolName,
                                                  sym.PartRef.RefID ),
                                RPT_SEVERITY_ERROR );
        }
    }
}


void CADSTAR_SCH_ARCHIVE_LOADER::loadBusses()
{
    for( std::pair<BUS_ID, BUS> busPair : Schematic.Buses )
    {
        BUS    bus     = busPair.second;
        bool   firstPt = true;
        VERTEX last;

        if( bus.LayerID != wxT( "NO_SHEET" ) )
        {
            SCH_SCREEN*                screen     = m_sheetMap.at( bus.LayerID )->GetScreen();
            std::shared_ptr<BUS_ALIAS> kiBusAlias = std::make_shared<BUS_ALIAS>();

            kiBusAlias->SetName( bus.Name );
            kiBusAlias->SetParent( screen );
            screen->AddBusAlias( kiBusAlias );
            m_busesMap.insert( { bus.ID, kiBusAlias } );

            SCH_LABEL* label = new SCH_LABEL();

            wxString busname = HandleTextOverbar( bus.Name );

            label->SetText( wxT( "{" ) + busname + wxT( "}" ) );
            label->SetVisible( true );
            screen->Append( label );

            SHAPE_LINE_CHAIN busLineChain; // to compute nearest segment to bus label

            for( const VERTEX& cur : bus.Shape.Vertices )
            {
                busLineChain.Append( getKiCadPoint( cur.End ) );

                if( firstPt )
                {
                    last    = cur;
                    firstPt = false;

                    if( !bus.HasBusLabel )
                    {
                        // Add a bus label on the starting point if the original CADSTAR design
                        // does not have an explicit label
                        label->SetPosition( getKiCadPoint( last.End ) );
                    }

                    continue;
                }


                SCH_LINE* kiBus = new SCH_LINE();

                kiBus->SetStartPoint( getKiCadPoint( last.End ) );
                kiBus->SetEndPoint( getKiCadPoint( cur.End ) );
                kiBus->SetLayer( LAYER_BUS );
                kiBus->SetLineWidth( getLineThickness( bus.LineCodeID ) );
                screen->Append( kiBus );

                last = cur;
            }

            if( bus.HasBusLabel )
            {
                //lets find the closest point in the busline to the label
                VECTOR2I busLabelLoc = getKiCadPoint( bus.BusLabel.Position );
                VECTOR2I nearestPt = busLineChain.NearestPoint( busLabelLoc );

                label->SetPosition( nearestPt );

                applyTextSettings( label,
                                   bus.BusLabel.TextCodeID,
                                   bus.BusLabel.Alignment,
                                   bus.BusLabel.Justification );

                // Re-set bus name as it might have been "double-escaped" after applyTextSettings
                label->SetText( wxT( "{" ) + busname + wxT( "}" ) );

                // Note orientation of the bus label will be determined in loadNets
                // (the position of the wire will determine how best to place the bus label)
            }
        }
    }
}


void CADSTAR_SCH_ARCHIVE_LOADER::loadNets()
{
    for( std::pair<NET_ID, NET_SCH> netPair : Schematic.Nets )
    {
        NET_SCH                             net     = netPair.second;
        wxString                            netName = net.Name;
        std::map<NETELEMENT_ID, SCH_LABEL*> netlabels;

        if( netName.IsEmpty() )
            netName = wxString::Format( "$%ld", net.SignalNum );

        netName = HandleTextOverbar( netName );

        for( std::pair<NETELEMENT_ID, NET_SCH::SYM_TERM> terminalPair : net.Terminals )
        {
            NET_SCH::SYM_TERM netTerm = terminalPair.second;

            if( m_powerSymMap.find( netTerm.SymbolID ) != m_powerSymMap.end() )
            {
                SCH_FIELD* val = m_powerSymMap.at( netTerm.SymbolID )->GetField( VALUE_FIELD );
                val->SetText( netName );
                val->SetBold( false );
                val->SetVisible( false );

                if( netTerm.HasNetLabel )
                {
                    val->SetVisible( true );
                    val->SetPosition( getKiCadPoint( netTerm.NetLabel.Position ) );

                    applyTextSettings( val,
                                       netTerm.NetLabel.TextCodeID,
                                       netTerm.NetLabel.Alignment,
                                       netTerm.NetLabel.Justification,
                                       netTerm.NetLabel.OrientAngle,
                                       netTerm.NetLabel.Mirror  );
                }
            }
            else if( m_globalLabelsMap.find( netTerm.SymbolID ) != m_globalLabelsMap.end() )
            {
                m_globalLabelsMap.at( netTerm.SymbolID )->SetText( netName );

                LAYER_ID sheet = Schematic.Symbols.at( netTerm.SymbolID ).LayerID;

                if( m_sheetMap.count( sheet ) )
                {
                    SCH_SCREEN* screen = m_sheetMap.at( sheet )->GetScreen();

                    // autoplace intersheet refs again since we've changed the name
                    m_globalLabelsMap.at( netTerm.SymbolID )->AutoplaceFields( screen, false );
                }
            }
            else if( !net.Name.IsEmpty() && Schematic.Symbols.count( netTerm.SymbolID )
                     && netTerm.HasNetLabel )
            {
                // This is a named net that connects to a schematic symbol pin - we need to put a label
                SCH_LABEL* label = new SCH_LABEL();
                label->SetText( netName );

                POINT pinLocation = getLocationOfNetElement( net, netTerm.ID );
                label->SetPosition( getKiCadPoint( pinLocation ) );
                label->SetVisible( true );

                applyTextSettings( label, netTerm.NetLabel.TextCodeID, netTerm.NetLabel.Alignment,
                                   netTerm.NetLabel.Justification );

                netlabels.insert( { netTerm.ID, label } );

                LAYER_ID sheet = Schematic.Symbols.at( netTerm.SymbolID ).LayerID;
                m_sheetMap.at( sheet )->GetScreen()->Append( label );
            }
        }

        auto getHierarchicalLabel =
                [&]( NETELEMENT_ID aNode ) -> SCH_HIERLABEL*
                {
                    if( aNode.Contains( "BLKT" ) )
                    {
                        NET_SCH::BLOCK_TERM blockTerm = net.BlockTerminals.at( aNode );
                        BLOCK_PIN_ID blockPinID = std::make_pair( blockTerm.BlockID,
                                                                  blockTerm.TerminalID );

                        if( m_sheetPinMap.find( blockPinID ) != m_sheetPinMap.end() )
                            return m_sheetPinMap.at( blockPinID );
                    }

                    return nullptr;
                };

        //Add net name to all hierarchical pins (block terminals in CADSTAR)
        for( std::pair<NETELEMENT_ID, NET_SCH::BLOCK_TERM> blockPair : net.BlockTerminals )
        {
            SCH_HIERLABEL* label = getHierarchicalLabel( blockPair.first );

            if( label )
                label->SetText( netName );
        }

        // Load all bus entries and add net label if required
        for( std::pair<NETELEMENT_ID, NET_SCH::BUS_TERM> busPair : net.BusTerminals )
        {
            NET_SCH::BUS_TERM busTerm = busPair.second;
            BUS               bus     = Schematic.Buses.at( busTerm.BusID );

            if( !alg::contains( m_busesMap.at( bus.ID )->Members(), netName ) )
                m_busesMap.at( bus.ID )->Members().emplace_back( netName );

            SCH_BUS_WIRE_ENTRY* busEntry =
                    new SCH_BUS_WIRE_ENTRY( getKiCadPoint( busTerm.FirstPoint ), false );

            VECTOR2I size =
                    getKiCadPoint( busTerm.SecondPoint ) - getKiCadPoint( busTerm.FirstPoint );
            busEntry->SetSize( VECTOR2I( size.x, size.y ) );

            m_sheetMap.at( bus.LayerID )->GetScreen()->Append( busEntry );

            // Always add a label at bus terminals to ensure connectivity.
            // If the original design does not have a label, just make it very small
            // to keep connectivity but make the design look visually similar to
            // the original.
            SCH_LABEL* label = new SCH_LABEL();
            label->SetText( netName );
            label->SetPosition( getKiCadPoint( busTerm.SecondPoint ) );
            label->SetVisible( true );

            if( busTerm.HasNetLabel )
            {
                applyTextSettings( label,
                                   busTerm.NetLabel.TextCodeID,
                                   busTerm.NetLabel.Alignment,
                                   busTerm.NetLabel.Justification );
            }
            else
            {
                label->SetTextSize( VECTOR2I( SMALL_LABEL_SIZE, SMALL_LABEL_SIZE ) );
            }

            netlabels.insert( { busTerm.ID, label } );
            m_sheetMap.at( bus.LayerID )->GetScreen()->Append( label );
        }

        for( std::pair<NETELEMENT_ID, NET_SCH::DANGLER> danglerPair : net.Danglers )
        {
            NET_SCH::DANGLER dangler = danglerPair.second;

            SCH_LABEL* label = new SCH_LABEL();
            label->SetPosition( getKiCadPoint( dangler.Position ) );
            label->SetVisible( true );

            if( dangler.HasNetLabel )
            {
                applyTextSettings( label,
                                   dangler.NetLabel.TextCodeID,
                                   dangler.NetLabel.Alignment,
                                   dangler.NetLabel.Justification );
            }

            label->SetText( netName ); // set text after applying settings to avoid double-escaping
            netlabels.insert( { dangler.ID, label } );

            m_sheetMap.at( dangler.LayerID )->GetScreen()->Append( label );
        }

        for( NET_SCH::CONNECTION_SCH conn : net.Connections )
        {
            if( conn.LayerID == wxT( "NO_SHEET" ) )
                continue; // No point loading virtual connections. KiCad handles that internally

            POINT start = getLocationOfNetElement( net, conn.StartNode );
            POINT end = getLocationOfNetElement( net, conn.EndNode );

            if( start.x == UNDEFINED_VALUE || end.x == UNDEFINED_VALUE )
                continue;

            // Connections in CADSTAR are always implied between symbols even if the route
            // doesn't start and end exactly at the connection points
            if( conn.Path.size() < 1 || conn.Path.front() != start )
                conn.Path.insert( conn.Path.begin(), start );

            if( conn.Path.size() < 2 || conn.Path.back() != end )
                conn.Path.push_back( end );

            bool      firstPt  = true;
            bool      secondPt = false;
            VECTOR2I  last;
            SCH_LINE* wire = nullptr;

            SHAPE_LINE_CHAIN wireChain; // Create a temp. line chain representing the connection

            for( POINT pt : conn.Path )
            {
                wireChain.Append( getKiCadPoint( pt ) );
            }

            // AUTO-FIX SHEET PINS
            //--------------------
            // KiCad constrains the sheet pin on the edge of the sheet object whereas in
            // CADSTAR it can be anywhere. Let's find the intersection of the wires with the sheet
            // and place the hierarchical
            std::vector<NETELEMENT_ID> nodes;
            nodes.push_back( conn.StartNode );
            nodes.push_back( conn.EndNode );

            for( NETELEMENT_ID node : nodes )
            {
                SCH_HIERLABEL* sheetPin = getHierarchicalLabel( node );

                if( sheetPin )
                {
                    if( sheetPin->Type() == SCH_SHEET_PIN_T
                            && SCH_SHEET::ClassOf( sheetPin->GetParent() ) )
                    {
                        SCH_SHEET* parentSheet   = static_cast<SCH_SHEET*>( sheetPin->GetParent() );
                        VECTOR2I   sheetSize = parentSheet->GetSize();
                        VECTOR2I   sheetPosition = parentSheet->GetPosition();

                        int leftSide  = sheetPosition.x;
                        int rightSide = sheetPosition.x + sheetSize.x;
                        int topSide   = sheetPosition.y;
                        int botSide   = sheetPosition.y + sheetSize.y;

                        SHAPE_LINE_CHAIN sheetEdge;

                        sheetEdge.Append( leftSide, topSide );
                        sheetEdge.Append( rightSide, topSide );
                        sheetEdge.Append( rightSide, botSide );
                        sheetEdge.Append( leftSide, botSide );
                        sheetEdge.Append( leftSide, topSide );

                        SHAPE_LINE_CHAIN::INTERSECTIONS wireToSheetIntersects;

                        if( !wireChain.Intersect( sheetEdge, wireToSheetIntersects ) )
                        {
                            // The block terminal is outside the block shape in the original
                            // CADSTAR design. Since KiCad's Sheet Pin will already be constrained
                            // on the edge, we will simply join to it with a straight line.
                            if( node == conn.StartNode )
                                wireChain = wireChain.Reverse();

                            wireChain.Append( sheetPin->GetPosition() );

                            if( node == conn.StartNode )
                                wireChain = wireChain.Reverse();
                        }
                        else
                        {
                            // The block terminal is either inside or on the shape edge. Lets use
                            // the first intersection point.
                            VECTOR2I intsctPt   = wireToSheetIntersects.at( 0 ).p;
                            int      intsctIndx = wireChain.FindSegment( intsctPt );
                            wxASSERT_MSG( intsctIndx != -1, "Can't find intersecting segment" );

                            if( node == conn.StartNode )
                                wireChain.Replace( 0, intsctIndx, intsctPt );
                            else
                                wireChain.Replace( intsctIndx + 1, /*end index*/ -1, intsctPt );

                            sheetPin->SetPosition( intsctPt );
                        }
                    }
                }
            }

            auto fixNetLabelsAndSheetPins =
                    [&]( const EDA_ANGLE& aWireAngle, NETELEMENT_ID& aNetEleID )
                    {
                        SPIN_STYLE spin = getSpinStyle( aWireAngle );

                        if( netlabels.find( aNetEleID ) != netlabels.end() )
                            netlabels.at( aNetEleID )->SetSpinStyle( spin.MirrorY() );

                        SCH_HIERLABEL* sheetPin = getHierarchicalLabel( aNetEleID );

                        if( sheetPin )
                            sheetPin->SetSpinStyle( spin.MirrorX() );
                    };

            // Now we can load the wires and fix the label orientations
            for( const VECTOR2I& pt : wireChain.CPoints() )
            {
                if( firstPt )
                {
                    last     = pt;
                    firstPt  = false;
                    secondPt = true;
                    continue;
                }

                if( secondPt )
                {
                    secondPt = false;

                    EDA_ANGLE wireAngle( last - pt );
                    fixNetLabelsAndSheetPins( wireAngle, conn.StartNode );
                }

                wire = new SCH_LINE();

                wire->SetStartPoint( last );
                wire->SetEndPoint( pt );
                wire->SetLayer( LAYER_WIRE );

                if( !conn.ConnectionLineCode.IsEmpty() )
                    wire->SetLineWidth( getLineThickness( conn.ConnectionLineCode ) );

                last = pt;

                m_sheetMap.at( conn.LayerID )->GetScreen()->Append( wire );
            }

            //Fix labels on the end wire
            if( wire )
            {
                EDA_ANGLE wireAngle( wire->GetEndPoint() - wire->GetStartPoint() );
                fixNetLabelsAndSheetPins( wireAngle, conn.EndNode );
            }
        }

        for( std::pair<NETELEMENT_ID, NET_SCH::JUNCTION_SCH> juncPair : net.Junctions )
        {
            NET_SCH::JUNCTION_SCH junc = juncPair.second;

            SCH_JUNCTION* kiJunc = new SCH_JUNCTION();

            kiJunc->SetPosition( getKiCadPoint( junc.Location ) );
            m_sheetMap.at( junc.LayerID )->GetScreen()->Append( kiJunc );

            if( junc.HasNetLabel )
            {
                // In CADSTAR the label can be placed anywhere, but in KiCad it has to be placed
                // in the same location as the junction for it to be connected to it.
                SCH_LABEL* label = new SCH_LABEL();
                label->SetText( netName );
                label->SetPosition( getKiCadPoint( junc.Location ) );
                label->SetVisible( true );

                EDA_ANGLE  labelAngle = getAngle( junc.NetLabel.OrientAngle );
                SPIN_STYLE spin = getSpinStyle( labelAngle );

                label->SetSpinStyle( spin );

                m_sheetMap.at( junc.LayerID )->GetScreen()->Append( label );
            }
        }
    }
}


void CADSTAR_SCH_ARCHIVE_LOADER::loadFigures()
{
    for( std::pair<FIGURE_ID, FIGURE> figPair : Schematic.Figures )
    {
        FIGURE fig = figPair.second;

        loadFigure( fig, fig.LayerID, LAYER_NOTES );
    }
}


void CADSTAR_SCH_ARCHIVE_LOADER::loadTexts()
{
    for( std::pair<TEXT_ID, TEXT> textPair : Schematic.Texts )
    {
        TEXT txt = textPair.second;

        SCH_TEXT* kiTxt = getKiCadSchText( txt );
        loadItemOntoKiCadSheet( txt.LayerID, kiTxt );
    }
}


void CADSTAR_SCH_ARCHIVE_LOADER::loadDocumentationSymbols()
{
    for( std::pair<DOCUMENTATION_SYMBOL_ID, DOCUMENTATION_SYMBOL> docSymPair :
         Schematic.DocumentationSymbols )
    {
        DOCUMENTATION_SYMBOL docSym = docSymPair.second;

        if( Library.SymbolDefinitions.find( docSym.SymdefID ) == Library.SymbolDefinitions.end() )
        {
            m_reporter->Report( wxString::Format( _( "Documentation Symbol '%s' refers to symbol "
                                                     "definition ID '%s' which does not exist in "
                                                     "the library. The symbol was not loaded." ),
                                                  docSym.ID,
                                                  docSym.SymdefID ),
                                RPT_SEVERITY_ERROR );
            continue;
        }

        SYMDEF_SCM docSymDef  = Library.SymbolDefinitions.at( docSym.SymdefID );
        VECTOR2I   moveVector = getKiCadPoint( docSym.Origin ) - getKiCadPoint( docSymDef.Origin );
        EDA_ANGLE  rotationAngle     = getAngle( docSym.OrientAngle );
        double     scalingFactor     = (double) docSym.ScaleRatioNumerator
                                        / (double) docSym.ScaleRatioDenominator;
        VECTOR2I   centreOfTransform = getKiCadPoint( docSymDef.Origin );
        bool       mirrorInvert      = docSym.Mirror;

        for( std::pair<FIGURE_ID, FIGURE> figPair : docSymDef.Figures )
        {
            FIGURE fig = figPair.second;

            loadFigure( fig, docSym.LayerID, LAYER_NOTES, moveVector, rotationAngle, scalingFactor,
                        centreOfTransform, mirrorInvert );
        }

        for( std::pair<TEXT_ID, TEXT> textPair : docSymDef.Texts )
        {
            TEXT txt = textPair.second;

            txt.Mirror = ( txt.Mirror ) ? !mirrorInvert : mirrorInvert;
            txt.OrientAngle = docSym.OrientAngle - txt.OrientAngle;

            SCH_TEXT* kiTxt = getKiCadSchText( txt );

            VECTOR2I newPosition = applyTransform( kiTxt->GetPosition(), moveVector, rotationAngle,
                                                   scalingFactor, centreOfTransform, mirrorInvert );

            int     newTxtWidth     = KiROUND( kiTxt->GetTextWidth() * scalingFactor );
            int     newTxtHeight    = KiROUND( kiTxt->GetTextHeight() * scalingFactor );
            int     newTxtThickness = KiROUND( kiTxt->GetTextThickness() * scalingFactor );

            kiTxt->SetPosition( newPosition );
            kiTxt->SetTextWidth( newTxtWidth );
            kiTxt->SetTextHeight( newTxtHeight );
            kiTxt->SetTextThickness( newTxtThickness );

            loadItemOntoKiCadSheet( docSym.LayerID, kiTxt );
        }
    }
}


void CADSTAR_SCH_ARCHIVE_LOADER::loadTextVariables()
{
    auto findAndReplaceTextField =
            [&]( TEXT_FIELD_NAME aField, wxString aValue )
            {
                if( m_context.TextFieldToValuesMap.find( aField ) != m_context.TextFieldToValuesMap.end() )
                {
                    if( m_context.TextFieldToValuesMap.at( aField ) != aValue )
                    {
                        m_context.TextFieldToValuesMap.at( aField ) = aValue;
                        m_context.InconsistentTextFields.insert( aField );
                        return false;
                    }
                }
                else
                {
                    m_context.TextFieldToValuesMap.insert( { aField, aValue } );
                }

                return true;
            };

    PROJECT* pj = &m_schematic->Prj();

    if( pj )
    {
        std::map<wxString, wxString>& txtVars = pj->GetTextVars();

        // Most of the design text fields can be derived from other elements
        if( Schematic.VariantHierarchy.Variants.size() > 0 )
        {
            VARIANT loadedVar = Schematic.VariantHierarchy.Variants.begin()->second;

            findAndReplaceTextField( TEXT_FIELD_NAME::VARIANT_NAME, loadedVar.Name );
            findAndReplaceTextField( TEXT_FIELD_NAME::VARIANT_DESCRIPTION, loadedVar.Description );
        }

        findAndReplaceTextField( TEXT_FIELD_NAME::DESIGN_TITLE, Header.JobTitle );

        for( std::pair<TEXT_FIELD_NAME, wxString> txtvalue : m_context.TextFieldToValuesMap )
        {
            wxString varName  = CADSTAR_TO_KICAD_FIELDS.at( txtvalue.first );
            wxString varValue = txtvalue.second;

            txtVars.insert( { varName, varValue } );
        }

        for( std::pair<wxString, wxString> txtvalue : m_context.FilenamesToTextMap )
        {
            wxString varName  = txtvalue.first;
            wxString varValue = txtvalue.second;

            txtVars.insert( { varName, varValue } );
        }
    }
    else
    {
        m_reporter->Report( _( "Text Variables could not be set as there is no project attached." ),
                            RPT_SEVERITY_ERROR );
    }
}


LIB_FIELD*
CADSTAR_SCH_ARCHIVE_LOADER::addNewFieldToSymbol( const wxString&              aFieldName,
                                                 std::unique_ptr<LIB_SYMBOL>& aKiCadSymbol )
{
    // First Check if field already exists
    LIB_FIELD* existingField = aKiCadSymbol->FindField( aFieldName );

    if( existingField != nullptr )
        return existingField;

    int        newfieldID = aKiCadSymbol->GetFieldCount();
    LIB_FIELD* newfield = new LIB_FIELD( aKiCadSymbol.get(), newfieldID );
    newfield->SetName( aFieldName );
    newfield->SetVisible( false );
    aKiCadSymbol->AddField( newfield );
    /*
    @todo we should load that a field is a URL by checking if it starts with "Link"
    e.g.:
    if( aFieldName.Lower().StartsWith( "link" ) )
        newfield->SetAsURL*/

    return newfield;
}


const LIB_SYMBOL* CADSTAR_SCH_ARCHIVE_LOADER::loadSymdef( const SYMDEF_ID& aSymdefID )
{
    wxCHECK( Library.SymbolDefinitions.find( aSymdefID ) != Library.SymbolDefinitions.end(), nullptr );

    if( m_symDefMap.count( aSymdefID ) )
        return m_symDefMap.at( aSymdefID ).get(); // return a non-owning ptr

    SYMDEF_SCM csSym = Library.SymbolDefinitions.at( aSymdefID );
    std::unique_ptr<LIB_SYMBOL> kiSym = std::make_unique<LIB_SYMBOL>( csSym.BuildLibName() );
    const int                   gateNumber = 1; // Always load to gate "A" - we will change the unit later

    // Load Graphical Figures
    for( std::pair<FIGURE_ID, FIGURE> figPair : csSym.Figures )
    {
        FIGURE         fig = figPair.second;
        int            lineThickness = getLineThickness( fig.LineCodeID );
        LINE_STYLE     linestyle = getLineStyle( fig.LineCodeID );

        if( fig.Shape.Type == SHAPE_TYPE::OPENSHAPE )
        {
            loadLibrarySymbolShapeVertices( fig.Shape.Vertices, csSym.Origin, kiSym.get(),
                                            gateNumber,
                                            lineThickness );
        }
        else
        {
            LIB_SHAPE* shape = new LIB_SHAPE( kiSym.get(), SHAPE_T::POLY );
            shape->SetPolyShape( fig.Shape.ConvertToPolySet(
                    [&]( const VECTOR2I& aPt )
                    {
                        return getKiCadLibraryPoint( aPt, csSym.Origin );
                    },
                    ARC_ACCURACY ) );

            shape->SetUnit( gateNumber );

            shape->SetStroke( STROKE_PARAMS( lineThickness, linestyle ) );

            if( fig.Shape.Type == SHAPE_TYPE::SOLID )
                shape->SetFillMode( FILL_T::FILLED_SHAPE );
            else if( fig.Shape.Type == SHAPE_TYPE::OUTLINE )
                shape->SetFillMode( FILL_T::NO_FILL );
            else if( fig.Shape.Type == SHAPE_TYPE::HATCHED ) // We don't have an equivalent
                shape->SetFillMode( FILL_T::FILLED_WITH_BG_BODYCOLOR );

            kiSym->AddDrawItem( shape );
        }
    }

    PINNUM_TO_TERMINAL_MAP pinNumToTerminals;

    // Load Pins
    for( std::pair<TERMINAL_ID, TERMINAL> termPair : csSym.Terminals )
    {
        TERMINAL term = termPair.second;
        wxString pinNum = wxString::Format( "%ld", term.ID );
        wxString pinName = wxEmptyString;
        std::unique_ptr<LIB_PIN> pin = std::make_unique<LIB_PIN>( kiSym.get() );

        // Assume passive pin for now (we will set it later once we load the parts)
        pin->SetType( ELECTRICAL_PINTYPE::PT_PASSIVE );

        pin->SetPosition( getKiCadLibraryPoint( term.Position, csSym.Origin ) );
        pin->SetLength( 0 ); //CADSTAR Pins are just a point (have no length)
        pin->SetShape( GRAPHIC_PINSHAPE::LINE );
        pin->SetUnit( gateNumber );
        pin->SetNumber( pinNum );
        pin->SetName( pinName );

        // TC0 is the default CADSTAR text size for name/number if none specified
        int pinNumberHeight = getTextHeightFromTextCode( wxT( "TC0" ) );
        int pinNameHeight = getTextHeightFromTextCode( wxT( "TC0" ) );

        if( csSym.PinNumberLocations.count( term.ID ) )
        {
            PIN_NUM_LABEL_LOC pinNumLocation = csSym.PinNumberLocations.at( term.ID );
            pinNumberHeight = getTextHeightFromTextCode( pinNumLocation.TextCodeID );
        }

        if( csSym.PinLabelLocations.count( term.ID ) )
        {
            PIN_NUM_LABEL_LOC pinNameLocation = csSym.PinLabelLocations.at( term.ID );
            pinNameHeight = getTextHeightFromTextCode( pinNameLocation.TextCodeID );
        }

        pin->SetNumberTextSize( pinNumberHeight );
        pin->SetNameTextSize( pinNameHeight );

        pinNumToTerminals.insert( { pin->GetNumber(), term.ID } );
        kiSym->AddDrawItem( pin.release() );
    }

    m_symDefTerminalsMap.insert( { aSymdefID, pinNumToTerminals } );
    fixUpLibraryPins( kiSym.get(), gateNumber );


    // Load Text items
    for( std::pair<TEXT_ID, TEXT> textPair : csSym.Texts )
    {
        TEXT csText = textPair.second;

        std::unique_ptr<LIB_TEXT> libtext = std::make_unique<LIB_TEXT>( kiSym.get() );
        libtext->SetText( csText.Text );
        libtext->SetUnit( gateNumber );
        libtext->SetPosition( getKiCadLibraryPoint( csText.Position, csSym.Origin ) );
        libtext->SetMultilineAllowed( true ); // temporarily so that we calculate bbox correctly

        applyTextSettings( libtext.get(), csText.TextCodeID, csText.Alignment, csText.Justification,
                           csText.OrientAngle, csText.Mirror );

        // Split out multi line text items into individual text elements
        if( csText.Text.Contains( "\n" ) )
        {
            wxArrayString strings;
            wxStringSplit( csText.Text, strings, '\n' );
            wxPoint firstLinePos;

            for( size_t ii = 0; ii < strings.size(); ++ii )
            {
                BOX2I    bbox = libtext->GetTextBox( ii, true );
                VECTOR2I linePos = { bbox.GetLeft(), -bbox.GetBottom() };

                RotatePoint( linePos, libtext->GetTextPos(), -libtext->GetTextAngle() );

                LIB_TEXT* textLine = static_cast<LIB_TEXT*>( libtext->Duplicate() );
                textLine->SetText( strings[ii] );
                textLine->SetHorizJustify( GR_TEXT_H_ALIGN_LEFT );
                textLine->SetVertJustify( GR_TEXT_V_ALIGN_BOTTOM );
                textLine->SetTextPos( linePos );

                // Multiline text not allowed in LIB_TEXT
                textLine->SetMultilineAllowed( false );
                kiSym->AddDrawItem( textLine );
            }
        }
        else
        {
            // Multiline text not allowed in LIB_TEXT
            libtext->SetMultilineAllowed( false );
            kiSym->AddDrawItem( libtext.release() );
        }
    }

    // CADSTAR uses TC1 when fields don't have explicit text/attribute location
    static const TEXTCODE_ID defaultTextCode = "TC1";

    // Load field locations (Attributes in CADSTAR)

    // Symbol name (e.g. R1)
    if( csSym.TextLocations.count( SYMBOL_NAME_ATTRID ) )
    {
        TEXT_LOCATION& textLoc = csSym.TextLocations.at( SYMBOL_NAME_ATTRID );
        applyToLibraryFieldAttribute( textLoc, csSym.Origin, &kiSym->GetReferenceField() );
    }
    else
    {
        applyTextCodeIfExists( &kiSym->GetReferenceField(), defaultTextCode );
    }

    // Always add the part name field (even if it doesn't have a specific location defined)
    LIB_FIELD* partField = addNewFieldToSymbol( PartNameFieldName, kiSym );
    wxCHECK( partField, nullptr );
    wxASSERT( partField->GetName() == PartNameFieldName );

    if( csSym.TextLocations.count( PART_NAME_ATTRID ) )
    {
        TEXT_LOCATION& textLoc = csSym.TextLocations.at( PART_NAME_ATTRID );
        applyToLibraryFieldAttribute( textLoc, csSym.Origin, partField );
    }
    else
    {
        applyTextCodeIfExists( partField, defaultTextCode );
    }

    partField->SetVisible( SymbolPartNameColor.IsVisible );


    for( auto& [attributeId, textLocation] : csSym.TextLocations )
    {
        if( attributeId == PART_NAME_ATTRID || attributeId == SYMBOL_NAME_ATTRID
            || attributeId == SIGNALNAME_ORIGIN_ATTRID || attributeId == LINK_ORIGIN_ATTRID )
        {
            continue;
        }

        wxString attributeName = getAttributeName( attributeId );
        LIB_FIELD* field = addNewFieldToSymbol( attributeName, kiSym );
        applyToLibraryFieldAttribute( textLocation, csSym.Origin, field );
    }


    for( auto& [attributeId, attrValue] : csSym.AttributeValues )
    {
        if( attributeId == PART_NAME_ATTRID || attributeId == SYMBOL_NAME_ATTRID
            || attributeId == SIGNALNAME_ORIGIN_ATTRID || attributeId == LINK_ORIGIN_ATTRID )
        {
            continue;
        }

        wxString   attributeName = getAttributeName( attributeId );
        LIB_FIELD* field = addNewFieldToSymbol( attributeName, kiSym );

        if( attrValue.HasLocation )
            applyToLibraryFieldAttribute( attrValue.AttributeLocation, csSym.Origin, field );
        else
            applyTextCodeIfExists( field, defaultTextCode );
    }


    m_symDefMap.insert( { aSymdefID, std::move( kiSym ) } );

    return m_symDefMap.at( aSymdefID ).get(); // return a non-owning ptr
}


void CADSTAR_SCH_ARCHIVE_LOADER::loadSymbolGateAndPartFields( const SYMDEF_ID& aSymdefID,
                                                 const PART& aCadstarPart,
                                                 const GATE_ID& aGateID,
                                                 LIB_SYMBOL* aSymbol )
{
    wxCHECK( Library.SymbolDefinitions.find( aSymdefID ) != Library.SymbolDefinitions.end(), /*void*/ );

    std::unique_ptr<LIB_SYMBOL> kiSymDef( loadSymdef( aSymdefID )->Duplicate() );
    wxCHECK( kiSymDef, /*void*/ );

    //todo: need to use unique_ptr more. For now just create it here and release at end of function
    std::unique_ptr<LIB_SYMBOL> tempSymbol( aSymbol );

    // Update the pin numbers to match those defined in the Cadstar part
    TERMINAL_TO_PINNUM_MAP pinNumMap;

    for( auto&& [storedPinNum, termID] : m_symDefTerminalsMap[aSymdefID] )
    {
        PART::DEFINITION::PIN csPin = getPartDefinitionPin( aCadstarPart, aGateID, termID );
        LIB_PIN*              pin = kiSymDef->GetPin( storedPinNum );

        wxString pinName = HandleTextOverbar( csPin.Label );
        wxString pinNum = HandleTextOverbar( csPin.Name );

        if( pinNum.IsEmpty() )
        {
            if( !csPin.Identifier.IsEmpty() )
                pinNum = csPin.Identifier;
            else if( csPin.ID == UNDEFINED_VALUE )
                pinNum = wxString::Format( "%ld", termID );
            else
                pinNum = wxString::Format( "%ld", csPin.ID );
        }

        pin->SetType( getKiCadPinType( csPin.Type ) );
        pin->SetNumber( pinNum );
        pin->SetName( pinName );

        pinNumMap.insert( { termID, pinNum } );
    }

    m_pinNumsMap.insert( { aCadstarPart.ID + aGateID, pinNumMap } );

    // COPY ITEMS
    int gateNumber = getKiCadUnitNumberFromGate( aGateID );
    copySymbolItems( kiSymDef, tempSymbol, gateNumber );

    // Hide the value field for now (it might get unhidden if an attribute exists in the cadstar
    // design with the text "Value"
    tempSymbol->GetValueField().SetVisible( false );


    LIB_FIELD* partNameField = tempSymbol->FindField( PartNameFieldName );

    if( partNameField )
        partNameField->SetText( EscapeFieldText( aCadstarPart.Name ) );

    const POINT& symDefOrigin = Library.SymbolDefinitions.at( aSymdefID ).Origin;
    wxString     footprintRefName = wxEmptyString;
    wxString     footprintAlternateName = wxEmptyString;

    auto loadLibraryField = [&]( const ATTRIBUTE_VALUE& aAttributeVal )
    {
        wxString attrName = getAttributeName( aAttributeVal.AttributeID );

        // Remove invalid field characters
        wxString attributeValue = aAttributeVal.Value;
        attributeValue.Replace( wxT( "\n" ), wxT( "\\n" ) );
        attributeValue.Replace( wxT( "\r" ), wxT( "\\r" ) );
        attributeValue.Replace( wxT( "\t" ), wxT( "\\t" ) );

        //TODO: Handle "links": In cadstar a field can be a "link" if its name starts
        // with the characters "Link ". Need to figure out how to convert them to
        // equivalent in KiCad.

        if( attrName == wxT( "(PartDefinitionNameStem)" ) )
        {
            //Space not allowed in Reference field
            attributeValue.Replace( wxT( " " ), "_" );
            tempSymbol->GetReferenceField().SetText( attributeValue );
            return;
        }
        else if( attrName == wxT( "(PartDescription)" ) )
        {
            tempSymbol->SetDescription( attributeValue );
            return;
        }
        else if( attrName == wxT( "(PartDefinitionReferenceName)" ) )
        {
            footprintRefName = attributeValue;
            return;
        }
        else if( attrName == wxT( "(PartDefinitionAlternateName)" ) )
        {
            footprintAlternateName = attributeValue;
            return;
        }

        bool attrIsNew = tempSymbol->FindField( attrName ) == nullptr;
        LIB_FIELD* attrField = addNewFieldToSymbol( attrName, tempSymbol );

        wxASSERT( attrField->GetName() == attrName );
        attrField->SetText( aAttributeVal.Value );
        attrField->SetUnit( gateNumber );

        const ATTRIBUTE_ID& attrid = aAttributeVal.AttributeID;
        attrField->SetVisible( isAttributeVisible( attrid ) );

        if( aAttributeVal.HasLocation )
        {
            // Check if the part itself defined a location for the field
            applyToLibraryFieldAttribute( aAttributeVal.AttributeLocation, symDefOrigin,
                                            attrField );
        }
        else if( attrIsNew )
        {
            attrField->SetVisible( false );
            applyTextSettings( attrField, wxT( "TC1" ), ALIGNMENT::NO_ALIGNMENT,
                                JUSTIFICATION::LEFT );
        }
    };

    // Load all attributes in the Part Definition
    for( auto& [attrId, attrVal] : aCadstarPart.Definition.AttributeValues )
        loadLibraryField( attrVal );

    // Load all attributes in the Part itself.
    for( auto& [attrId, attrVal] : aCadstarPart.AttributeValues )
        loadLibraryField( attrVal );

    setFootprintOnSymbol( tempSymbol, footprintRefName, footprintAlternateName );

    if( aCadstarPart.Definition.HidePinNames )
    {
        tempSymbol->SetShowPinNames( false );
        tempSymbol->SetShowPinNumbers( false );
    }

    aSymbol = tempSymbol.release();
}


void CADSTAR_SCH_ARCHIVE_LOADER::setFootprintOnSymbol( std::unique_ptr<LIB_SYMBOL>& aKiCadSymbol,
                                                       const wxString& aFootprintName,
                                                       const wxString& aFootprintAlternate )
{
    wxString fpNameInLibrary = generateLibName( aFootprintName, aFootprintAlternate );

    if( !fpNameInLibrary.IsEmpty() )
    {
        wxArrayString fpFilters;
        fpFilters.Add( aFootprintName ); // In cadstar one footprint has several "alternates"

        if( !aFootprintAlternate.IsEmpty() )
            fpFilters.Add( fpNameInLibrary );

        aKiCadSymbol->SetFPFilters( fpFilters );

        LIB_ID libID( m_footprintLibName, fpNameInLibrary );
        aKiCadSymbol->GetFootprintField().SetText( libID.Format() );
    }
}


void CADSTAR_SCH_ARCHIVE_LOADER::loadLibrarySymbolShapeVertices( const std::vector<VERTEX>& aCadstarVertices,
                                                                 VECTOR2I aSymbolOrigin,
                                                                 LIB_SYMBOL* aSymbol,
                                                                 int aGateNumber,
                                                                 int aLineThickness )
{
    const VERTEX* prev = &aCadstarVertices.at( 0 );
    const VERTEX* cur;

    wxASSERT_MSG( prev->Type == VERTEX_TYPE::POINT, "First vertex should always be a point." );

    for( size_t i = 1; i < aCadstarVertices.size(); i++ )
    {
        cur = &aCadstarVertices.at( i );

        LIB_SHAPE* shape      = nullptr;
        bool       cw         = false;
        VECTOR2I startPoint = getKiCadLibraryPoint( prev->End, aSymbolOrigin );
        VECTOR2I endPoint   = getKiCadLibraryPoint( cur->End, aSymbolOrigin );
        VECTOR2I centerPoint;

        if( cur->Type == VERTEX_TYPE::ANTICLOCKWISE_SEMICIRCLE
                || cur->Type == VERTEX_TYPE::CLOCKWISE_SEMICIRCLE )
        {
            centerPoint = ( startPoint + endPoint ) / 2;
        }
        else
        {
            centerPoint = getKiCadLibraryPoint( cur->Center, aSymbolOrigin );
        }


        switch( cur->Type )
        {
        case VERTEX_TYPE::POINT:
            shape = new LIB_SHAPE( aSymbol, SHAPE_T::POLY );
            shape->AddPoint( startPoint );
            shape->AddPoint( endPoint );
            break;

        case VERTEX_TYPE::CLOCKWISE_SEMICIRCLE:
        case VERTEX_TYPE::CLOCKWISE_ARC:
            cw = true;
            KI_FALLTHROUGH;

        case VERTEX_TYPE::ANTICLOCKWISE_SEMICIRCLE:
        case VERTEX_TYPE::ANTICLOCKWISE_ARC:
            shape = new LIB_SHAPE( aSymbol, SHAPE_T::ARC );

            shape->SetPosition( centerPoint );

            if( cw )
            {
                shape->SetStart( endPoint );
                shape->SetEnd( startPoint );
            }
            else
            {
                shape->SetStart( startPoint );
                shape->SetEnd( endPoint );
            }

            break;
        }

        shape->SetUnit( aGateNumber );
        shape->SetStroke( STROKE_PARAMS( aLineThickness, LINE_STYLE::SOLID ) );
        aSymbol->AddDrawItem( shape, false );

        prev = cur;
    }

    aSymbol->GetDrawItems().sort();
}


void CADSTAR_SCH_ARCHIVE_LOADER::applyToLibraryFieldAttribute(
        const ATTRIBUTE_LOCATION& aCadstarAttrLoc, VECTOR2I aSymbolOrigin, LIB_FIELD* aKiCadField )
{
    aKiCadField->SetTextPos( getKiCadLibraryPoint( aCadstarAttrLoc.Position, aSymbolOrigin ) );

    applyTextSettings( aKiCadField,
                       aCadstarAttrLoc.TextCodeID,
                       aCadstarAttrLoc.Alignment,
                       aCadstarAttrLoc.Justification,
                       aCadstarAttrLoc.OrientAngle,
                       aCadstarAttrLoc.Mirror );
}


SCH_SYMBOL* CADSTAR_SCH_ARCHIVE_LOADER::loadSchematicSymbol( const SYMBOL& aCadstarSymbol,
                                                             const LIB_SYMBOL& aKiCadPart,
                                                             EDA_ANGLE& aComponentOrientation )
{
    LIB_ID libId;
    libId.SetLibItemName( aKiCadPart.GetName() );

    int unit = getKiCadUnitNumberFromGate( aCadstarSymbol.GateID );

    SCH_SHEET_PATH sheetpath;
    SCH_SHEET* kiSheet = m_sheetMap.at( aCadstarSymbol.LayerID );
    m_rootSheet->LocatePathOfScreen( kiSheet->GetScreen(), &sheetpath );

    SCH_SYMBOL* symbol = new SCH_SYMBOL( aKiCadPart, libId, &sheetpath, unit );

    if( aCadstarSymbol.IsComponent )
    {
        symbol->SetRef( &sheetpath, aCadstarSymbol.ComponentRef.Designator );
    }

    symbol->SetPosition( getKiCadPoint( aCadstarSymbol.Origin ) );

    EDA_ANGLE compAngle = getAngle( aCadstarSymbol.OrientAngle );
    int       compOrientation = 0;

    if( aCadstarSymbol.Mirror )
    {
        compAngle = -compAngle;
        compOrientation += SYMBOL_ORIENTATION_T::SYM_MIRROR_Y;
    }

    compOrientation += getComponentOrientation( compAngle, aComponentOrientation );
    EDA_ANGLE test1( compAngle );
    EDA_ANGLE test2( aComponentOrientation );

    if( test1.Normalize180() != test2.Normalize180() )
    {
        m_reporter->Report( wxString::Format( _( "Symbol '%s' is rotated by an angle of %.1f "
                                                 "degrees in the original CADSTAR design but "
                                                 "KiCad only supports rotation angles multiples "
                                                 "of 90 degrees. The connecting wires will need "
                                                 "manual fixing." ),
                                              aCadstarSymbol.ComponentRef.Designator,
                                              compAngle.AsDegrees() ),
                            RPT_SEVERITY_ERROR );
    }

    symbol->SetOrientation( compOrientation );

    if( m_sheetMap.find( aCadstarSymbol.LayerID ) == m_sheetMap.end() )
    {
        m_reporter->Report( wxString::Format( _( "Symbol '%s' references sheet ID '%s' which does "
                                                 "not exist in the design. The symbol was not "
                                                 "loaded." ),
                                              aCadstarSymbol.ComponentRef.Designator,
                                              aCadstarSymbol.LayerID ),
                            RPT_SEVERITY_ERROR );

        delete symbol;
        return nullptr;
    }

    wxString gate = ( aCadstarSymbol.GateID.IsEmpty() ) ? wxString( wxT( "A" ) ) : aCadstarSymbol.GateID;
    wxString partGateIndex = aCadstarSymbol.PartRef.RefID + gate;

    //Handle pin swaps
    if( m_pinNumsMap.find( partGateIndex ) != m_pinNumsMap.end() )
    {
        TERMINAL_TO_PINNUM_MAP termNumMap = m_pinNumsMap.at( partGateIndex );

        std::map<wxString, LIB_PIN*> pinNumToLibPinMap;

        for( auto& term : termNumMap )
        {
            wxString pinNum = term.second;
            pinNumToLibPinMap.insert( { pinNum,
                                        symbol->GetLibSymbolRef()->GetPin( term.second ) } );
        }

        auto replacePinNumber =
                [&]( wxString aOldPinNum, wxString aNewPinNum )
                {
                    if( aOldPinNum == aNewPinNum )
                        return;

                    LIB_PIN* libpin = pinNumToLibPinMap.at( aOldPinNum );
                    libpin->SetNumber( HandleTextOverbar( aNewPinNum ) );
                };

        //Older versions of Cadstar used pin numbers
        for( auto& pinPair : aCadstarSymbol.PinNumbers )
        {
            SYMBOL::PIN_NUM pin = pinPair.second;

            replacePinNumber( termNumMap.at( pin.TerminalID ),
                              wxString::Format( "%ld", pin.PinNum ) );
        }

        //Newer versions of Cadstar use pin names
        for( auto& pinPair : aCadstarSymbol.PinNames )
        {
            SYMPINNAME_LABEL pin = pinPair.second;
            replacePinNumber( termNumMap.at( pin.TerminalID ), pin.NameOrLabel );
        }

        symbol->UpdatePins();
    }

    kiSheet->GetScreen()->Append( symbol );

    return symbol;
}


void CADSTAR_SCH_ARCHIVE_LOADER::loadSymbolFieldAttribute( const ATTRIBUTE_LOCATION& aCadstarAttrLoc,
                                                           const EDA_ANGLE& aComponentOrientation,
                                                           bool aIsMirrored,
                                                           SCH_FIELD* aKiCadField )
{
    aKiCadField->SetPosition( getKiCadPoint( aCadstarAttrLoc.Position ) );
    aKiCadField->SetVisible( true );

    ALIGNMENT alignment = aCadstarAttrLoc.Alignment;
    EDA_ANGLE textAngle = getAngle( aCadstarAttrLoc.OrientAngle );

    if( aIsMirrored )
    {
        // We need to change the aligment when the symbol is mirrored based on the text orientation
        // To ensure the anchor point is the same in KiCad.

        int textIsVertical = KiROUND( textAngle.AsDegrees() / 90.0 ) % 2;

        if( textIsVertical )
            alignment = rotate180( alignment );

        alignment = mirrorX( alignment );
    }

    applyTextSettings( aKiCadField,
                       aCadstarAttrLoc.TextCodeID,
                       alignment,
                       aCadstarAttrLoc.Justification,
                       getCadstarAngle( textAngle - aComponentOrientation ),
                       aCadstarAttrLoc.Mirror );
}


int CADSTAR_SCH_ARCHIVE_LOADER::getComponentOrientation( const EDA_ANGLE& aOrientAngle,
                                                         EDA_ANGLE& aReturnedOrientation )
{
    int compOrientation = SYMBOL_ORIENTATION_T::SYM_ORIENT_0;

    EDA_ANGLE oDeg = aOrientAngle;
    oDeg.Normalize180();

    if( oDeg >= -ANGLE_45 && oDeg <= ANGLE_45 )
    {
        compOrientation      = SYMBOL_ORIENTATION_T::SYM_ORIENT_0;
        aReturnedOrientation = ANGLE_0;
    }
    else if( oDeg >= ANGLE_45 && oDeg <= ANGLE_135 )
    {
        compOrientation      = SYMBOL_ORIENTATION_T::SYM_ORIENT_90;
        aReturnedOrientation = ANGLE_90;
    }
    else if( oDeg >= ANGLE_135 || oDeg <= -ANGLE_135 )
    {
        compOrientation      = SYMBOL_ORIENTATION_T::SYM_ORIENT_180;
        aReturnedOrientation = ANGLE_180;
    }
    else
    {
        compOrientation      = SYMBOL_ORIENTATION_T::SYM_ORIENT_270;
        aReturnedOrientation = ANGLE_270;
    }

    return compOrientation;
}


CADSTAR_SCH_ARCHIVE_LOADER::POINT
CADSTAR_SCH_ARCHIVE_LOADER::getLocationOfNetElement( const NET_SCH&       aNet,
                                                     const NETELEMENT_ID& aNetElementID )
{
    // clang-format off
    auto logUnknownNetElementError =
        [&]()
        {
            m_reporter->Report( wxString::Format( _( "Net %s references unknown net element %s. "
                                                     "The net was not properly loaded and may "
                                                     "require manual fixing." ),
                                                  getNetName( aNet ),
                                                  aNetElementID ),
                                RPT_SEVERITY_ERROR );

            return POINT();
        };
    // clang-format on

    if( aNetElementID.Contains( "J" ) ) // Junction
    {
        if( aNet.Junctions.find( aNetElementID ) == aNet.Junctions.end() )
            return logUnknownNetElementError();

        return aNet.Junctions.at( aNetElementID ).Location;
    }
    else if( aNetElementID.Contains( "P" ) ) // Terminal/Pin of a symbol
    {
        if( aNet.Terminals.find( aNetElementID ) == aNet.Terminals.end() )
            return logUnknownNetElementError();

        SYMBOL_ID   symid  = aNet.Terminals.at( aNetElementID ).SymbolID;
        TERMINAL_ID termid = aNet.Terminals.at( aNetElementID ).TerminalID;

        if( Schematic.Symbols.find( symid ) == Schematic.Symbols.end() )
            return logUnknownNetElementError();

        SYMBOL    sym          = Schematic.Symbols.at( symid );
        SYMDEF_ID symdefid     = sym.SymdefID;
        VECTOR2I  symbolOrigin = sym.Origin;

        if( Library.SymbolDefinitions.find( symdefid ) == Library.SymbolDefinitions.end() )
            return logUnknownNetElementError();

        VECTOR2I libpinPosition =
                Library.SymbolDefinitions.at( symdefid ).Terminals.at( termid ).Position;
        VECTOR2I libOrigin = Library.SymbolDefinitions.at( symdefid ).Origin;

        VECTOR2I pinOffset = libpinPosition - libOrigin;
        pinOffset.x = ( pinOffset.x * sym.ScaleRatioNumerator ) / sym.ScaleRatioDenominator;
        pinOffset.y = ( pinOffset.y * sym.ScaleRatioNumerator ) / sym.ScaleRatioDenominator;

        VECTOR2I  pinPosition = symbolOrigin + pinOffset;
        EDA_ANGLE compAngle = getAngle( sym.OrientAngle );

        if( sym.Mirror )
            pinPosition.x = ( 2 * symbolOrigin.x ) - pinPosition.x;

        EDA_ANGLE adjustedOrientation;
        getComponentOrientation( compAngle, adjustedOrientation );

        RotatePoint( pinPosition, symbolOrigin, -adjustedOrientation );

        POINT retval;
        retval.x = pinPosition.x;
        retval.y = pinPosition.y;

        return retval;
    }
    else if( aNetElementID.Contains( "BT" ) ) // Bus Terminal
    {
        if( aNet.BusTerminals.find( aNetElementID ) == aNet.BusTerminals.end() )
            return logUnknownNetElementError();

        return aNet.BusTerminals.at( aNetElementID ).SecondPoint;
    }
    else if( aNetElementID.Contains( "BLKT" ) ) // Block Terminal (sheet hierarchy connection)
    {
        if( aNet.BlockTerminals.find( aNetElementID ) == aNet.BlockTerminals.end() )
            return logUnknownNetElementError();

        BLOCK_ID    blockid = aNet.BlockTerminals.at( aNetElementID ).BlockID;
        TERMINAL_ID termid  = aNet.BlockTerminals.at( aNetElementID ).TerminalID;

        if( Schematic.Blocks.find( blockid ) == Schematic.Blocks.end() )
            return logUnknownNetElementError();

        return Schematic.Blocks.at( blockid ).Terminals.at( termid ).Position;
    }
    else if( aNetElementID.Contains( "D" ) ) // Dangler
    {
        if( aNet.Danglers.find( aNetElementID ) == aNet.Danglers.end() )
            return logUnknownNetElementError();

        return aNet.Danglers.at( aNetElementID ).Position;
    }
    else
    {
        return logUnknownNetElementError();
    }
}


wxString CADSTAR_SCH_ARCHIVE_LOADER::getNetName( const NET_SCH& aNet )
{
    wxString netname = aNet.Name;

    if( netname.IsEmpty() )
        netname = wxString::Format( "$%ld", aNet.SignalNum );

    return netname;
}


void CADSTAR_SCH_ARCHIVE_LOADER::loadShapeVertices( const std::vector<VERTEX>& aCadstarVertices,
                                                    LINECODE_ID aCadstarLineCodeID,
                                                    LAYER_ID aCadstarSheetID,
                                                    SCH_LAYER_ID aKiCadSchLayerID,
                                                    const VECTOR2I& aMoveVector,
                                                    const EDA_ANGLE& aRotation,
                                                    const double& aScalingFactor,
                                                    const VECTOR2I& aTransformCentre,
                                                    const bool& aMirrorInvert )
{
    int lineWidth = KiROUND( getLineThickness( aCadstarLineCodeID ) * aScalingFactor );
    LINE_STYLE lineStyle = getLineStyle( aCadstarLineCodeID );

    const VERTEX* prev = &aCadstarVertices.at( 0 );
    const VERTEX* cur;

    wxASSERT_MSG( prev->Type == VERTEX_TYPE::POINT,
                  "First vertex should always be a point vertex" );

    auto pointTransform =
        [&]( const VECTOR2I& aV )
        {
            return applyTransform( getKiCadPoint( aV ), aMoveVector, aRotation,
                                   aScalingFactor, aTransformCentre, aMirrorInvert );
        };

    for( size_t ii = 1; ii < aCadstarVertices.size(); ii++ )
    {
        cur = &aCadstarVertices.at( ii );

        VECTOR2I transformedStartPoint = pointTransform( prev->End );
        VECTOR2I transformedEndPoint = pointTransform( cur->End );

        switch( cur->Type )
        {
        case VERTEX_TYPE::CLOCKWISE_SEMICIRCLE:
        case VERTEX_TYPE::CLOCKWISE_ARC:
        case VERTEX_TYPE::ANTICLOCKWISE_SEMICIRCLE:
        case VERTEX_TYPE::ANTICLOCKWISE_ARC:
        {
            SHAPE_ARC tempArc = cur->BuildArc( transformedStartPoint, pointTransform );

            SCH_SHAPE* arcShape = new SCH_SHAPE( SHAPE_T::ARC, lineWidth );
            arcShape->SetArcGeometry( tempArc.GetP0(), tempArc.GetArcMid(), tempArc.GetP1() );

            loadItemOntoKiCadSheet( aCadstarSheetID, arcShape );
        }
            break;

        case VERTEX_TYPE::POINT:
        {
            SCH_LINE* segment = new SCH_LINE();

            segment->SetLayer( aKiCadSchLayerID );
            segment->SetLineWidth( lineWidth );
            segment->SetLineStyle( lineStyle );

            segment->SetStartPoint( transformedStartPoint );
            segment->SetEndPoint( transformedEndPoint );

            loadItemOntoKiCadSheet( aCadstarSheetID, segment );
         }
            break;

        default:
            wxFAIL_MSG( "Unknown CADSTAR Vertex type" );
        }


        prev = cur;
    }
}


void CADSTAR_SCH_ARCHIVE_LOADER::loadFigure( const FIGURE& aCadstarFigure,
                                             const LAYER_ID& aCadstarSheetIDOverride,
                                             SCH_LAYER_ID aKiCadSchLayerID,
                                             const VECTOR2I& aMoveVector,
                                             const EDA_ANGLE& aRotation,
                                             const double& aScalingFactor,
                                             const VECTOR2I& aTransformCentre,
                                             const bool& aMirrorInvert )
{
    loadShapeVertices( aCadstarFigure.Shape.Vertices, aCadstarFigure.LineCodeID,
                       aCadstarSheetIDOverride, aKiCadSchLayerID, aMoveVector, aRotation,
                       aScalingFactor, aTransformCentre, aMirrorInvert );

    for( CUTOUT cutout : aCadstarFigure.Shape.Cutouts )
    {
        loadShapeVertices( cutout.Vertices, aCadstarFigure.LineCodeID, aCadstarSheetIDOverride,
                           aKiCadSchLayerID, aMoveVector, aRotation, aScalingFactor,
                           aTransformCentre, aMirrorInvert );
    }
}


void CADSTAR_SCH_ARCHIVE_LOADER::loadSheetAndChildSheets( LAYER_ID              aCadstarSheetID,
                                                          const VECTOR2I&       aPosition,
                                                          VECTOR2I              aSheetSize,
                                                          const SCH_SHEET_PATH& aParentSheet )
{
    wxCHECK_MSG( m_sheetMap.find( aCadstarSheetID ) == m_sheetMap.end(), ,
                 "Sheet already loaded!" );

    SCH_SHEET*     sheet = new SCH_SHEET(
        /* aParent */ aParentSheet.Last(),
        /* aPosition */ aPosition,
        /* aSize */ VECTOR2I( aSheetSize ) );
    SCH_SCREEN*    screen = new SCH_SCREEN( m_schematic );
    SCH_SHEET_PATH instance( aParentSheet );

    sheet->SetScreen( screen );

    wxString name = Sheets.SheetNames.at( aCadstarSheetID );

    SCH_FIELD& sheetNameField = sheet->GetFields()[SHEETNAME];
    SCH_FIELD& filenameField  = sheet->GetFields()[SHEETFILENAME];

    sheetNameField.SetText( name );

    int         sheetNum = getSheetNumber( aCadstarSheetID );
    wxString    loadedFilename = wxFileName( Filename ).GetName();
    std::string filename = wxString::Format( "%s_%02d", loadedFilename, sheetNum ).ToStdString();

    ReplaceIllegalFileNameChars( &filename );
    filename += wxT( "." ) + FILEEXT::KiCadSchematicFileExtension;

    filenameField.SetText( filename );

    wxFileName fn( m_schematic->Prj().GetProjectPath() + filename );
    sheet->GetScreen()->SetFileName( fn.GetFullPath() );
    aParentSheet.Last()->GetScreen()->Append( sheet );
    instance.push_back( sheet );

    wxString pageNumStr = wxString::Format( "%d", getSheetNumber( aCadstarSheetID ) );
    instance.SetPageNumber( pageNumStr );

    sheet->AutoplaceFields( /* aScreen */ nullptr, /* aManual */ false );

    m_sheetMap.insert( { aCadstarSheetID, sheet } );

    loadChildSheets( aCadstarSheetID, instance );
}


void CADSTAR_SCH_ARCHIVE_LOADER::loadChildSheets( LAYER_ID aCadstarSheetID,
                                                  const SCH_SHEET_PATH& aSheet )
{
    wxCHECK_MSG( m_sheetMap.find( aCadstarSheetID ) != m_sheetMap.end(), ,
                 "FIXME! Parent sheet should be loaded before attempting to load subsheets" );

    for( std::pair<BLOCK_ID, BLOCK> blockPair : Schematic.Blocks )
    {
        BLOCK& block = blockPair.second;

        if( block.LayerID == aCadstarSheetID && block.Type == BLOCK::TYPE::CHILD )
        {
            if( block.AssocLayerID == wxT( "NO_LINK" ) )
            {
                if( block.Figures.size() > 0 )
                {
                    m_reporter->Report( wxString::Format( _( "The block ID %s (Block name: '%s') "
                                                             "is drawn on sheet '%s' but is not "
                                                             "linked to another sheet in the "
                                                             "design. KiCad requires all sheet "
                                                             "symbols to be associated to a sheet, "
                                                             "so the block was not loaded." ),
                                                          block.ID, block.Name,
                                                          Sheets.SheetNames.at( aCadstarSheetID ) ),
                                        RPT_SEVERITY_ERROR );
                }

                continue;
            }

            // In KiCad you can only draw rectangular shapes whereas in Cadstar arbitrary shapes
            // are allowed. We will calculate the extents of the Cadstar shape and draw a rectangle

            std::pair<VECTOR2I, VECTOR2I> blockExtents;

            if( block.Figures.size() > 0 )
            {
                blockExtents = getFigureExtentsKiCad( block.Figures.begin()->second );
            }
            else
            {
                THROW_IO_ERROR( wxString::Format( _( "The CADSTAR schematic might be corrupt: "
                                                     "Block %s references a child sheet but has no "
                                                     "Figure defined." ),
                                                  block.ID ) );
            }

            loadSheetAndChildSheets( block.AssocLayerID, blockExtents.first, blockExtents.second,
                                     aSheet );

            // Hide all KiCad sheet properties (sheet name/filename is not applicable in CADSTAR)
            SCH_SHEET* loadedSheet = m_sheetMap.at( block.AssocLayerID );
            SCH_FIELDS fields = loadedSheet->GetFields();

            for( SCH_FIELD& field : fields )
            {
                field.SetVisible( false );
            }

            if( block.HasBlockLabel )
            {
                //@todo use below code when KiCad supports multi-line fields
                /*
                // Add the block label as a separate field
                SCH_FIELD blockNameField( getKiCadPoint( block.BlockLabel.Position ), 2,
                        loadedSheet, wxString( "Block name" ) );
                blockNameField.SetText( block.Name );
                blockNameField.SetVisible( true );

                applyTextSettings( &blockNameField,
                                    block.BlockLabel.TextCodeID,
                                    block.BlockLabel.Alignment,
                                    block.BlockLabel.Justification,
                                    block.BlockLabel.OrientAngle,
                                    block.BlockLabel.Mirror );

                fields.push_back( blockNameField );*/

                // For now as as a text item (supports multi-line properly)
                SCH_TEXT* kiTxt = new SCH_TEXT();

                kiTxt->SetParent( m_schematic );
                kiTxt->SetPosition( getKiCadPoint( block.BlockLabel.Position ) );
                kiTxt->SetText( block.Name );

                applyTextSettings( kiTxt,
                                    block.BlockLabel.TextCodeID,
                                    block.BlockLabel.Alignment,
                                    block.BlockLabel.Justification,
                                    block.BlockLabel.OrientAngle,
                                    block.BlockLabel.Mirror );

                loadItemOntoKiCadSheet( aCadstarSheetID, kiTxt );
            }

            loadedSheet->SetFields( fields );
        }
    }
}


std::vector<CADSTAR_SCH_ARCHIVE_LOADER::LAYER_ID> CADSTAR_SCH_ARCHIVE_LOADER::findOrphanSheets()
{
    std::vector<LAYER_ID> childSheets, orphanSheets;

    //Find all sheets that are child of another
    for( std::pair<BLOCK_ID, BLOCK> blockPair : Schematic.Blocks )
    {
        BLOCK&    block        = blockPair.second;
        LAYER_ID& assocSheetID = block.AssocLayerID;

        if( block.Type == BLOCK::TYPE::CHILD )
            childSheets.push_back( assocSheetID );
    }

    //Add sheets that do not have a parent
    for( LAYER_ID sheetID : Sheets.SheetOrder )
    {
        if( std::find( childSheets.begin(), childSheets.end(), sheetID ) == childSheets.end() )
            orphanSheets.push_back( sheetID );
    }

    return orphanSheets;
}


int CADSTAR_SCH_ARCHIVE_LOADER::getSheetNumber( LAYER_ID aCadstarSheetID )
{
    int i = 1;

    for( LAYER_ID sheetID : Sheets.SheetOrder )
    {
        if( sheetID == aCadstarSheetID )
            return i;

        ++i;
    }

    return -1;
}


void CADSTAR_SCH_ARCHIVE_LOADER::loadItemOntoKiCadSheet( LAYER_ID aCadstarSheetID, SCH_ITEM* aItem )
{
    wxCHECK_MSG( aItem, /*void*/, wxT( "aItem is null" ) );

    if( aCadstarSheetID == "ALL_SHEETS" )
    {
        SCH_ITEM* duplicateItem = nullptr;

        for( std::pair<LAYER_ID, SHEET_NAME> sheetPair : Sheets.SheetNames )
        {
            LAYER_ID sheetID = sheetPair.first;
            duplicateItem    = aItem->Duplicate();
            m_sheetMap.at( sheetID )->GetScreen()->Append( aItem->Duplicate() );
        }

        //Get rid of the extra copy:
        delete aItem;
        aItem = duplicateItem;
    }
    else if( aCadstarSheetID == "NO_SHEET" )
    {
        wxFAIL_MSG( wxT( "Trying to add an item to NO_SHEET? This might be a documentation symbol." ) );
    }
    else
    {
        if( m_sheetMap.find( aCadstarSheetID ) != m_sheetMap.end() )
        {
            m_sheetMap.at( aCadstarSheetID )->GetScreen()->Append( aItem );
        }
        else
        {
            delete aItem;
            wxFAIL_MSG( wxT( "Unknown Sheet ID." ) );
        }
    }
}


CADSTAR_SCH_ARCHIVE_LOADER::SYMDEF_ID
CADSTAR_SCH_ARCHIVE_LOADER::getSymDefFromName( const wxString& aSymdefName,
                                               const wxString& aSymDefAlternate )
{
    if( m_SymDefNamesCache.size() != Library.SymbolDefinitions.size() )
    {
        // Re-initialise
        m_SymDefNamesCache.clear();
        m_DefaultSymDefNamesCache.clear();

        // Create a lower case cache to avoid searching each time
        for( auto& [id, symdef] : Library.SymbolDefinitions )
        {
            wxString refKey = symdef.ReferenceName.Lower();
            wxString altKey = symdef.Alternate.Lower();

            m_SymDefNamesCache[{ refKey, altKey }] = id;

            // Secondary cache to find symbols just by the Name (e.g. if the alternate
            // does not exist, we still want to return a symbo - the same behaviour
            // as CADSTAR

            if( !m_DefaultSymDefNamesCache.count( refKey ) )
            {
                m_DefaultSymDefNamesCache.insert( { refKey, id } );
            }
            else if( altKey.IsEmpty() )
            {
                // Always use the empty alternate if it exists
                m_DefaultSymDefNamesCache[refKey] = id;
            }
        }
    }

    wxString refKeyToFind = aSymdefName.Lower();
    wxString altKeyToFind = aSymDefAlternate.Lower();

    if( m_SymDefNamesCache.count( { refKeyToFind, altKeyToFind } ) )
    {
        return m_SymDefNamesCache[{ refKeyToFind, altKeyToFind }];
    }
    else if( m_DefaultSymDefNamesCache.count( refKeyToFind ) )
    {
        return m_DefaultSymDefNamesCache[refKeyToFind];
    }


    return SYMDEF_ID();
}


bool CADSTAR_SCH_ARCHIVE_LOADER::isAttributeVisible( const ATTRIBUTE_ID& aCadstarAttributeID )
{
    // Use CADSTAR visibility settings to determine if an attribute is visible
    if( AttrColors.AttributeColors.find( aCadstarAttributeID ) != AttrColors.AttributeColors.end() )
    {
        return AttrColors.AttributeColors.at( aCadstarAttributeID ).IsVisible;
    }

    return false; // If there is no visibility setting, assume not displayed
}


int CADSTAR_SCH_ARCHIVE_LOADER::getLineThickness( const LINECODE_ID& aCadstarLineCodeID )
{
    wxCHECK( Assignments.Codedefs.LineCodes.find( aCadstarLineCodeID )
                     != Assignments.Codedefs.LineCodes.end(),
             schIUScale.MilsToIU( DEFAULT_WIRE_WIDTH_MILS ) );

    return getKiCadLength( Assignments.Codedefs.LineCodes.at( aCadstarLineCodeID ).Width );
}


LINE_STYLE CADSTAR_SCH_ARCHIVE_LOADER::getLineStyle( const LINECODE_ID& aCadstarLineCodeID )
{
    wxCHECK( Assignments.Codedefs.LineCodes.find( aCadstarLineCodeID )
                     != Assignments.Codedefs.LineCodes.end(),
             LINE_STYLE::SOLID );

    // clang-format off
    switch( Assignments.Codedefs.LineCodes.at( aCadstarLineCodeID ).Style )
    {
    case LINESTYLE::DASH:       return LINE_STYLE::DASH;
    case LINESTYLE::DASHDOT:    return LINE_STYLE::DASHDOT;
    case LINESTYLE::DASHDOTDOT: return LINE_STYLE::DASHDOT; //TODO: update in future
    case LINESTYLE::DOT:        return LINE_STYLE::DOT;
    case LINESTYLE::SOLID:      return LINE_STYLE::SOLID;
    default:                    return LINE_STYLE::DEFAULT;
    }
    // clang-format on
}


CADSTAR_SCH_ARCHIVE_LOADER::TEXTCODE
CADSTAR_SCH_ARCHIVE_LOADER::getTextCode( const TEXTCODE_ID& aCadstarTextCodeID )
{
    wxCHECK( Assignments.Codedefs.TextCodes.find( aCadstarTextCodeID )
                     != Assignments.Codedefs.TextCodes.end(),
             TEXTCODE() );

    return Assignments.Codedefs.TextCodes.at( aCadstarTextCodeID );
}


int CADSTAR_SCH_ARCHIVE_LOADER::getTextHeightFromTextCode( const TEXTCODE_ID& aCadstarTextCodeID )
{
    TEXTCODE txtCode = getTextCode( aCadstarTextCodeID );

    return KiROUND( (double) getKiCadLength( txtCode.Height ) * TXT_HEIGHT_RATIO );
}


wxString CADSTAR_SCH_ARCHIVE_LOADER::getAttributeName( const ATTRIBUTE_ID& aCadstarAttributeID )
{
    wxCHECK( Assignments.Codedefs.AttributeNames.find( aCadstarAttributeID )
                     != Assignments.Codedefs.AttributeNames.end(),
             aCadstarAttributeID );

    return Assignments.Codedefs.AttributeNames.at( aCadstarAttributeID ).Name;
}


CADSTAR_SCH_ARCHIVE_LOADER::PART
CADSTAR_SCH_ARCHIVE_LOADER::getPart( const PART_ID& aCadstarPartID )
{
    wxCHECK( Parts.PartDefinitions.find( aCadstarPartID ) != Parts.PartDefinitions.end(), PART() );

    return Parts.PartDefinitions.at( aCadstarPartID );
}


CADSTAR_SCH_ARCHIVE_LOADER::ROUTECODE
CADSTAR_SCH_ARCHIVE_LOADER::getRouteCode( const ROUTECODE_ID& aCadstarRouteCodeID )
{
    wxCHECK( Assignments.Codedefs.RouteCodes.find( aCadstarRouteCodeID )
                     != Assignments.Codedefs.RouteCodes.end(),
             ROUTECODE() );

    return Assignments.Codedefs.RouteCodes.at( aCadstarRouteCodeID );
}


CADSTAR_SCH_ARCHIVE_LOADER::PART::DEFINITION::PIN
CADSTAR_SCH_ARCHIVE_LOADER::getPartDefinitionPin( const PART& aCadstarPart, const GATE_ID& aGateID,
                                                  const TERMINAL_ID& aTerminalID )
{
    for( std::pair<PART_DEFINITION_PIN_ID, PART::DEFINITION::PIN> pinPair :
         aCadstarPart.Definition.Pins )
    {
        PART::DEFINITION::PIN partPin = pinPair.second;

        if( partPin.TerminalGate == aGateID && partPin.TerminalPin == aTerminalID )
            return partPin;
    }

    return PART::DEFINITION::PIN();
}


ELECTRICAL_PINTYPE CADSTAR_SCH_ARCHIVE_LOADER::getKiCadPinType( const CADSTAR_PIN_TYPE& aPinType )
{
    switch( aPinType )
    {
    case CADSTAR_PIN_TYPE::UNCOMMITTED:        return ELECTRICAL_PINTYPE::PT_PASSIVE;
    case CADSTAR_PIN_TYPE::INPUT:              return ELECTRICAL_PINTYPE::PT_INPUT;
    case CADSTAR_PIN_TYPE::OUTPUT_OR:          return ELECTRICAL_PINTYPE::PT_OPENCOLLECTOR;
    case CADSTAR_PIN_TYPE::OUTPUT_NOT_OR:      return ELECTRICAL_PINTYPE::PT_OUTPUT;
    case CADSTAR_PIN_TYPE::OUTPUT_NOT_NORM_OR: return ELECTRICAL_PINTYPE::PT_OUTPUT;
    case CADSTAR_PIN_TYPE::POWER:              return ELECTRICAL_PINTYPE::PT_POWER_IN;
    case CADSTAR_PIN_TYPE::GROUND:             return ELECTRICAL_PINTYPE::PT_POWER_IN;
    case CADSTAR_PIN_TYPE::TRISTATE_BIDIR:     return ELECTRICAL_PINTYPE::PT_BIDI;
    case CADSTAR_PIN_TYPE::TRISTATE_INPUT:     return ELECTRICAL_PINTYPE::PT_INPUT;
    case CADSTAR_PIN_TYPE::TRISTATE_DRIVER:    return ELECTRICAL_PINTYPE::PT_OUTPUT;
    }

    return ELECTRICAL_PINTYPE::PT_UNSPECIFIED;
}

int CADSTAR_SCH_ARCHIVE_LOADER::getKiCadUnitNumberFromGate( const GATE_ID& aCadstarGateID )
{
    if( aCadstarGateID.IsEmpty() )
        return 1;

    return (int) aCadstarGateID.Upper().GetChar( 0 ) - (int) wxUniChar( 'A' ) + 1;
}


SPIN_STYLE CADSTAR_SCH_ARCHIVE_LOADER::getSpinStyle( const long long& aCadstarOrientation,
                                                     bool aMirror )
{
    EDA_ANGLE  orientation = getAngle( aCadstarOrientation );
    SPIN_STYLE spinStyle   = getSpinStyle( orientation );

    if( aMirror )
    {
        spinStyle = spinStyle.RotateCCW();
        spinStyle = spinStyle.RotateCCW();
    }

    return spinStyle;
}


SPIN_STYLE CADSTAR_SCH_ARCHIVE_LOADER::getSpinStyle( const EDA_ANGLE& aOrientation )
{
    SPIN_STYLE spinStyle = SPIN_STYLE::LEFT;

    EDA_ANGLE oDeg = aOrientation;
    oDeg.Normalize180();

    if( oDeg >= -ANGLE_45 && oDeg <= ANGLE_45 )
        spinStyle = SPIN_STYLE::RIGHT;                  // 0deg
    else if( oDeg >= ANGLE_45 && oDeg <= ANGLE_135 )
        spinStyle = SPIN_STYLE::UP;                     // 90deg
    else if( oDeg >= ANGLE_135 || oDeg <= -ANGLE_135 )
        spinStyle = SPIN_STYLE::LEFT;                   // 180deg
    else
        spinStyle = SPIN_STYLE::BOTTOM;                 // 270deg

    return spinStyle;
}


CADSTAR_SCH_ARCHIVE_LOADER::ALIGNMENT
CADSTAR_SCH_ARCHIVE_LOADER::mirrorX( const ALIGNMENT& aCadstarAlignment )
{
    switch( aCadstarAlignment )
    {
    // Change left to right:
    case ALIGNMENT::NO_ALIGNMENT:
    case ALIGNMENT::BOTTOMLEFT:    return ALIGNMENT::BOTTOMRIGHT;
    case ALIGNMENT::CENTERLEFT:    return ALIGNMENT::CENTERRIGHT;
    case ALIGNMENT::TOPLEFT:       return ALIGNMENT::TOPRIGHT;

    //Change right to left:
    case ALIGNMENT::BOTTOMRIGHT:   return ALIGNMENT::BOTTOMLEFT;
    case ALIGNMENT::CENTERRIGHT:   return ALIGNMENT::CENTERLEFT;
    case ALIGNMENT::TOPRIGHT:      return ALIGNMENT::TOPLEFT;

    // Center alignment does not mirror:
    case ALIGNMENT::BOTTOMCENTER:
    case ALIGNMENT::CENTERCENTER:
    case ALIGNMENT::TOPCENTER:     return aCadstarAlignment;

    // Shouldn't be here
    default: wxFAIL_MSG( "Unknown Cadstar Alignment" ); return aCadstarAlignment;
    }
}


CADSTAR_SCH_ARCHIVE_LOADER::ALIGNMENT
CADSTAR_SCH_ARCHIVE_LOADER::rotate180( const ALIGNMENT& aCadstarAlignment )
{
    switch( aCadstarAlignment )
    {
    case ALIGNMENT::NO_ALIGNMENT:
    case ALIGNMENT::BOTTOMLEFT:    return ALIGNMENT::TOPRIGHT;
    case ALIGNMENT::BOTTOMCENTER:  return ALIGNMENT::TOPCENTER;
    case ALIGNMENT::BOTTOMRIGHT:   return ALIGNMENT::TOPLEFT;
    case ALIGNMENT::TOPLEFT:       return ALIGNMENT::BOTTOMRIGHT;
    case ALIGNMENT::TOPCENTER:     return ALIGNMENT::BOTTOMCENTER;
    case ALIGNMENT::TOPRIGHT:      return ALIGNMENT::BOTTOMLEFT;
    case ALIGNMENT::CENTERLEFT:    return ALIGNMENT::CENTERRIGHT;
    case ALIGNMENT::CENTERCENTER:  return ALIGNMENT::CENTERCENTER;
    case ALIGNMENT::CENTERRIGHT:   return ALIGNMENT::CENTERLEFT;

    // Shouldn't be here
    default: wxFAIL_MSG( "Unknown Cadstar Alignment" ); return aCadstarAlignment;
    }
}


void CADSTAR_SCH_ARCHIVE_LOADER::applyTextCodeIfExists( EDA_TEXT*          aKiCadTextItem,
                                                        const TEXTCODE_ID& aCadstarTextCodeID )
{
    // Ensure we have no Cadstar overbar characters
    wxString escapedText = HandleTextOverbar( aKiCadTextItem->GetText() );
    aKiCadTextItem->SetText( escapedText );

    if( !Assignments.Codedefs.TextCodes.count( aCadstarTextCodeID ) )
        return;

    TEXTCODE textCode = getTextCode( aCadstarTextCodeID );
    int      textHeight = KiROUND( (double) getKiCadLength( textCode.Height ) * TXT_HEIGHT_RATIO );
    int      textWidth = getKiCadLength( textCode.Width );

    // The width is zero for all non-cadstar fonts. Using a width equal to 2/3 the height seems
    // to work well for most fonts.
    if( textWidth == 0 )
        textWidth = getKiCadLength( 2LL * textCode.Height / 3LL );

    aKiCadTextItem->SetTextWidth( textWidth );
    aKiCadTextItem->SetTextHeight( textHeight );

#if 0
    // EEschema currently supports only normal vs bold for text thickness.
    aKiCadTextItem->SetTextThickness( getKiCadLength( textCode.LineWidth ) );
#endif

    aKiCadTextItem->SetBold( textCode.Font.Modifier1 == FONT_BOLD );
    aKiCadTextItem->SetItalic( textCode.Font.Italic );
}


void CADSTAR_SCH_ARCHIVE_LOADER::applyTextSettings( EDA_TEXT*            aKiCadTextItem,
                                                    const TEXTCODE_ID&   aCadstarTextCodeID,
                                                    const ALIGNMENT&     aCadstarAlignment,
                                                    const JUSTIFICATION& aCadstarJustification,
                                                    const long long      aCadstarOrientAngle,
                                                    bool                 aMirrored )
{
    applyTextCodeIfExists( aKiCadTextItem, aCadstarTextCodeID );
    aKiCadTextItem->SetTextAngle( getAngle( aCadstarOrientAngle ) );

    // Justification ignored for now as not supported in Eeschema, but leaving this code in
    // place for future upgrades.
    // TODO update this when Eeschema supports justification independent of anchor position.
    ALIGNMENT textAlignment = aCadstarAlignment;

    // KiCad mirrors the justification and alignment when the symbol is mirrored but CADSTAR
    // specifies it post-mirroring. In contrast, if the text item itself is mirrored (not
    // supported in KiCad), CADSTAR specifies the alignment and justification pre-mirroring
    if( aMirrored )
        textAlignment = mirrorX( aCadstarAlignment );

    auto setAlignment =
            [&]( EDA_TEXT* aText, ALIGNMENT aAlignment )
            {
                switch( aAlignment )
                {
                case ALIGNMENT::NO_ALIGNMENT: // Bottom left of the first line
                    //No exact KiCad equivalent, so lets move the position of the text
                    FixTextPositionNoAlignment( aText );
                    KI_FALLTHROUGH;
                case ALIGNMENT::BOTTOMLEFT:
                    aText->SetVertJustify( GR_TEXT_V_ALIGN_BOTTOM );
                    aText->SetHorizJustify( GR_TEXT_H_ALIGN_LEFT );
                    break;

                case ALIGNMENT::BOTTOMCENTER:
                    aText->SetVertJustify( GR_TEXT_V_ALIGN_BOTTOM );
                    aText->SetHorizJustify( GR_TEXT_H_ALIGN_CENTER );
                    break;

                case ALIGNMENT::BOTTOMRIGHT:
                    aText->SetVertJustify( GR_TEXT_V_ALIGN_BOTTOM );
                    aText->SetHorizJustify( GR_TEXT_H_ALIGN_RIGHT );
                    break;

                case ALIGNMENT::CENTERLEFT:
                    aText->SetVertJustify( GR_TEXT_V_ALIGN_CENTER );
                    aText->SetHorizJustify( GR_TEXT_H_ALIGN_LEFT );
                    break;

                case ALIGNMENT::CENTERCENTER:
                    aText->SetVertJustify( GR_TEXT_V_ALIGN_CENTER );
                    aText->SetHorizJustify( GR_TEXT_H_ALIGN_CENTER );
                    break;

                case ALIGNMENT::CENTERRIGHT:
                    aText->SetVertJustify( GR_TEXT_V_ALIGN_CENTER );
                    aText->SetHorizJustify( GR_TEXT_H_ALIGN_RIGHT );
                    break;

                case ALIGNMENT::TOPLEFT:
                    aText->SetVertJustify( GR_TEXT_V_ALIGN_TOP );
                    aText->SetHorizJustify( GR_TEXT_H_ALIGN_LEFT );
                    break;

                case ALIGNMENT::TOPCENTER:
                    aText->SetVertJustify( GR_TEXT_V_ALIGN_TOP );
                    aText->SetHorizJustify( GR_TEXT_H_ALIGN_CENTER );
                    break;

                case ALIGNMENT::TOPRIGHT:
                    aText->SetVertJustify( GR_TEXT_V_ALIGN_TOP );
                    aText->SetHorizJustify( GR_TEXT_H_ALIGN_RIGHT );
                    break;
                }
            };

    SPIN_STYLE spin = getSpinStyle( aCadstarOrientAngle, aMirrored );
    EDA_ITEM*  textEdaItem = dynamic_cast<EDA_ITEM*>( aKiCadTextItem );
    wxCHECK( textEdaItem, /* void */ ); // ensure this is a EDA_ITEM

    switch( textEdaItem->Type() )
    {
    // Some KiCad schematic text items only permit a limited amount of angles
    // and text justifications
    case LIB_TEXT_T:
    case SCH_FIELD_T:
    case LIB_FIELD_T:
    {
        // Spin style not used. All text justifications are permitted. However, only orientations
        // of 0 deg or 90 deg are supported
        EDA_ANGLE angle = aKiCadTextItem->GetTextAngle();
        angle.Normalize();

        int quadrant = KiROUND( angle.AsDegrees() / 90.0 );
        quadrant %= 4;

        switch( quadrant )
        {
        case 0:
            angle = ANGLE_HORIZONTAL;
            break;
        case 1:
            angle = ANGLE_VERTICAL;
            break;
        case 2:
            angle = ANGLE_HORIZONTAL;
            textAlignment = rotate180( textAlignment );
            break;
        case 3:
            angle = ANGLE_VERTICAL;
            textAlignment = rotate180( textAlignment );
            break;
        default:
            wxFAIL_MSG( "Unknown Quadrant" );
        }

        aKiCadTextItem->SetTextAngle( angle );
        setAlignment( aKiCadTextItem, textAlignment );
        return;
    }

    case SCH_TEXT_T:
    {
        // Note spin style in a SCH_TEXT results in a vertical alignment GR_TEXT_V_ALIGN_BOTTOM
        // so need to adjust the location of the text element based on Cadstar's original text
        // alignment (anchor position).
        setAlignment( aKiCadTextItem, textAlignment );
        BOX2I    bb = textEdaItem->GetBoundingBox();
        int      off = static_cast<SCH_TEXT*>( aKiCadTextItem )->GetTextOffset();
        VECTOR2I pos;

        // Change the anchor point of the text item to make it match the same bounding box
        // And correct the error introduced by the text offsetting in KiCad
        switch( spin )
        {
        case SPIN_STYLE::BOTTOM: pos = { bb.GetRight() - off, bb.GetTop()          }; break;
        case SPIN_STYLE::UP:     pos = { bb.GetRight() - off, bb.GetBottom()       }; break;
        case SPIN_STYLE::LEFT:   pos = { bb.GetRight()      , bb.GetBottom() + off }; break;
        case SPIN_STYLE::RIGHT:  pos = { bb.GetLeft()       , bb.GetBottom() + off }; break;
        }

        aKiCadTextItem->SetTextPos( pos );

        switch( spin )
        {
        case SPIN_STYLE::RIGHT:            // Horiz Normal Orientation
            aKiCadTextItem->SetTextAngle( ANGLE_HORIZONTAL );
            aKiCadTextItem->SetHorizJustify( GR_TEXT_H_ALIGN_LEFT );
            break;

        case SPIN_STYLE::UP:               // Vert Orientation UP
            aKiCadTextItem->SetTextAngle( ANGLE_VERTICAL );
            aKiCadTextItem->SetHorizJustify( GR_TEXT_H_ALIGN_LEFT );
            break;

        case SPIN_STYLE::LEFT:             // Horiz Orientation - Right justified
            aKiCadTextItem->SetTextAngle( ANGLE_HORIZONTAL );
            aKiCadTextItem->SetHorizJustify( GR_TEXT_H_ALIGN_RIGHT );
            break;

        case SPIN_STYLE::BOTTOM:           //  Vert Orientation BOTTOM
            aKiCadTextItem->SetTextAngle( ANGLE_VERTICAL );
            aKiCadTextItem->SetHorizJustify( GR_TEXT_H_ALIGN_RIGHT );
            break;
        }

        aKiCadTextItem->SetVertJustify( GR_TEXT_V_ALIGN_BOTTOM );
        break;
    }

    // We don't want to change position of net labels as that would break connectivity
    case SCH_LABEL_T:
    case SCH_GLOBAL_LABEL_T:
    case SCH_HIER_LABEL_T:
    case SCH_SHEET_PIN_T:
        static_cast<SCH_LABEL_BASE*>( aKiCadTextItem )->SetSpinStyle( spin );
        return;

    default:
        wxFAIL_MSG( "Unexpected item type" );
        return;
    }
}


SCH_TEXT* CADSTAR_SCH_ARCHIVE_LOADER::getKiCadSchText( const TEXT& aCadstarTextElement )
{
    SCH_TEXT* kiTxt = new SCH_TEXT();

    kiTxt->SetParent( m_schematic ); // set to the schematic for now to avoid asserts
    kiTxt->SetPosition( getKiCadPoint( aCadstarTextElement.Position ) );
    kiTxt->SetText( aCadstarTextElement.Text );

    applyTextSettings( kiTxt,
                       aCadstarTextElement.TextCodeID,
                       aCadstarTextElement.Alignment,
                       aCadstarTextElement.Justification,
                       aCadstarTextElement.OrientAngle,
                       aCadstarTextElement.Mirror );

    return kiTxt;
}


LIB_SYMBOL* CADSTAR_SCH_ARCHIVE_LOADER::getScaledLibPart( const LIB_SYMBOL* aSymbol,
                                                          long long aScalingFactorNumerator,
                                                          long long aScalingFactorDenominator )
{
    LIB_SYMBOL* retval = new LIB_SYMBOL( *aSymbol );

    if( aScalingFactorNumerator == aScalingFactorDenominator )
        return retval; // 1:1 scale, nothing to do

    auto scaleLen =
        [&]( int aLength ) -> int
        {
            return( aLength * aScalingFactorNumerator ) / aScalingFactorDenominator;
        };

    auto scalePt =
        [&]( VECTOR2I aCoord ) -> VECTOR2I
        {
            return VECTOR2I( scaleLen( aCoord.x ), scaleLen( aCoord.y ) );
        };

    auto scaleSize =
        [&]( VECTOR2I aSize ) -> VECTOR2I
        {
            return VECTOR2I( scaleLen( aSize.x ), scaleLen( aSize.y ) );
        };

    LIB_ITEMS_CONTAINER& items = retval->GetDrawItems();

    for( LIB_ITEM& item : items )
    {
        switch( item.Type() )
        {
        case KICAD_T::LIB_SHAPE_T:
        {
            LIB_SHAPE& shape = static_cast<LIB_SHAPE&>( item );

            if( shape.GetShape() == SHAPE_T::ARC )
            {
                shape.SetPosition( scalePt( shape.GetPosition() ) );
                shape.SetStart( scalePt( shape.GetStart() ) );
                shape.SetEnd( scalePt( shape.GetEnd() ) );
            }
            else if( shape.GetShape() == SHAPE_T::POLY )
            {
                SHAPE_LINE_CHAIN& poly = shape.GetPolyShape().Outline( 0 );

                for( size_t ii = 0; ii < poly.GetPointCount(); ++ii )
                    poly.SetPoint( ii, scalePt( poly.CPoint( ii ) ) );
            }
            break;
        }

        case KICAD_T::LIB_PIN_T:
        {
            LIB_PIN& pin = static_cast<LIB_PIN&>( item );

            pin.SetPosition( scalePt( pin.GetPosition() ) );
            pin.SetLength( scaleLen( pin.GetLength() ) );
            break;
        }

        case KICAD_T::LIB_TEXT_T:
        {
            LIB_TEXT& txt = static_cast<LIB_TEXT&>( item );

            txt.SetPosition( scalePt( txt.GetPosition() ) );
            txt.SetTextSize( scaleSize( txt.GetTextSize() ) );
            break;
        }

        default:
            break;
        }

    }

    return retval;
}


void CADSTAR_SCH_ARCHIVE_LOADER::fixUpLibraryPins( LIB_SYMBOL* aSymbolToFix, int aGateNumber )
{
    auto compLambda = []( const VECTOR2I& aA, const VECTOR2I& aB )
    {
        return LexicographicalCompare( aA, aB ) < 0;
    };

    // Store a list of vertical or horizontal segments in the symbol
    // Note: Need the custom comparison function to ensure the map is sorted correctly
    std::map<VECTOR2I, SHAPE_LINE_CHAIN, decltype( compLambda )> uniqueSegments( compLambda );

    LIB_ITEMS_CONTAINER::ITERATOR shapeIt = aSymbolToFix->GetDrawItems().begin( LIB_SHAPE_T );

    for( ; shapeIt != aSymbolToFix->GetDrawItems().end( LIB_SHAPE_T ); ++shapeIt )
    {
        LIB_SHAPE& shape = static_cast<LIB_SHAPE&>( *shapeIt );

        if( aGateNumber > 0 && shape.GetUnit() != aGateNumber )
            continue;

        if( shape.GetShape() != SHAPE_T::POLY )
            continue;

        SHAPE_LINE_CHAIN poly = shape.GetPolyShape().Outline( 0 );

        if( poly.GetPointCount() == 2 )
        {
            VECTOR2I pt0 = poly.CPoint( 0 );
            VECTOR2I pt1 = poly.CPoint( 1 );

            if( pt0 != pt1 && uniqueSegments.count( pt0 ) == 0 && uniqueSegments.count( pt1 ) == 0 )
            {
                // we are only interested in vertical or horizontal segments
                if( pt0.x == pt1.x || pt0.y == pt1.y )
                {
                    uniqueSegments.insert( { pt0, poly } );
                    uniqueSegments.insert( { pt1, poly } );
                }
            }
        }
    }

    LIB_PINS pins;
    aSymbolToFix->GetPins( pins, aGateNumber );

    for( auto& pin : pins )
    {
        auto setPinOrientation =
                [&]( const EDA_ANGLE& aAngle )
                {
                    EDA_ANGLE angle( aAngle );
                    angle.Normalize180();

                    if( angle >= -ANGLE_45 && angle <= ANGLE_45 )
                        pin->SetOrientation( PIN_ORIENTATION::PIN_RIGHT ); // 0 degrees
                    else if( angle >= ANGLE_45 && angle <= ANGLE_135 )
                        pin->SetOrientation( PIN_ORIENTATION::PIN_UP ); // 90 degrees
                    else if( angle >= ANGLE_135 || angle <= -ANGLE_135 )
                        pin->SetOrientation( PIN_ORIENTATION::PIN_LEFT ); // 180 degrees
                    else
                        pin->SetOrientation( PIN_ORIENTATION::PIN_DOWN ); // -90 degrees
                };

        if( uniqueSegments.count( pin->GetPosition() ) )
        {
            SHAPE_LINE_CHAIN& poly = uniqueSegments.at( pin->GetPosition() );

            VECTOR2I otherPt = poly.CPoint( 0 );

            if( otherPt == pin->GetPosition() )
                otherPt = poly.CPoint( 1 );

            VECTOR2I vec( otherPt - pin->GetPosition() );

            pin->SetLength( vec.EuclideanNorm() );
            setPinOrientation( EDA_ANGLE( vec ) );
        }
    }
}


std::pair<VECTOR2I, VECTOR2I>
CADSTAR_SCH_ARCHIVE_LOADER::getFigureExtentsKiCad( const FIGURE& aCadstarFigure )
{
    VECTOR2I upperLeft( Assignments.Settings.DesignLimit.x, 0 );
    VECTOR2I lowerRight( 0, Assignments.Settings.DesignLimit.y );

    for( const VERTEX& v : aCadstarFigure.Shape.Vertices )
    {
        if( upperLeft.x > v.End.x )
            upperLeft.x = v.End.x;

        if( upperLeft.y < v.End.y )
            upperLeft.y = v.End.y;

        if( lowerRight.x < v.End.x )
            lowerRight.x = v.End.x;

        if( lowerRight.y > v.End.y )
            lowerRight.y = v.End.y;
    }

    for( CUTOUT cutout : aCadstarFigure.Shape.Cutouts )
    {
        for( const VERTEX& v : aCadstarFigure.Shape.Vertices )
        {
            if( upperLeft.x > v.End.x )
                upperLeft.x = v.End.x;

            if( upperLeft.y < v.End.y )
                upperLeft.y = v.End.y;

            if( lowerRight.x < v.End.x )
                lowerRight.x = v.End.x;

            if( lowerRight.y > v.End.y )
                lowerRight.y = v.End.y;
        }
    }

    VECTOR2I upperLeftKiCad = getKiCadPoint( upperLeft );
    VECTOR2I lowerRightKiCad = getKiCadPoint( lowerRight );

    VECTOR2I size = lowerRightKiCad - upperLeftKiCad;

    return { upperLeftKiCad, VECTOR2I( abs( size.x ), abs( size.y ) ) };
}


VECTOR2I CADSTAR_SCH_ARCHIVE_LOADER::getKiCadPoint( const VECTOR2I& aCadstarPoint )
{
    VECTOR2I retval;

    retval.x = getKiCadLength( aCadstarPoint.x - m_designCenter.x );
    retval.y = -getKiCadLength( aCadstarPoint.y - m_designCenter.y );

    return retval;
}


VECTOR2I CADSTAR_SCH_ARCHIVE_LOADER::getKiCadLibraryPoint( const VECTOR2I& aCadstarPoint,
                                                           const VECTOR2I& aCadstarCentre )
{
    VECTOR2I retval;

    retval.x = getKiCadLength( aCadstarPoint.x - aCadstarCentre.x );
    retval.y = getKiCadLength( aCadstarPoint.y - aCadstarCentre.y );

    return retval;
}


VECTOR2I CADSTAR_SCH_ARCHIVE_LOADER::applyTransform( const VECTOR2I& aPoint,
                                                     const VECTOR2I& aMoveVector,
                                                     const EDA_ANGLE& aRotation,
                                                     const double& aScalingFactor,
                                                     const VECTOR2I& aTransformCentre,
                                                     const bool& aMirrorInvert )
{
    VECTOR2I retVal = aPoint;

    if( aScalingFactor != 1.0 )
    {
        //scale point
        retVal -= aTransformCentre;
        retVal.x = KiROUND( retVal.x * aScalingFactor );
        retVal.y = KiROUND( retVal.y * aScalingFactor );
        retVal += aTransformCentre;
    }

    if( aMirrorInvert )
        MIRROR( retVal.x, aTransformCentre.x );

    if( !aRotation.IsZero() )
        RotatePoint( retVal, aTransformCentre, aRotation );

    if( aMoveVector != VECTOR2I{ 0, 0 } )
        retVal += aMoveVector;

    return retVal;
}


double CADSTAR_SCH_ARCHIVE_LOADER::getPolarRadius( const VECTOR2I& aPoint )
{
    return sqrt( ( (double) aPoint.x * (double) aPoint.x )
                 + ( (double) aPoint.y * (double) aPoint.y ) );
}
