/*
 * This program source code file is part of KICAD, a free EDA CAD application.
 *
 * Copyright (C) 2016-2018 CERN
 * Copyright (C) 2019  KiCad Developers, see AUTHORS.txt for contributors.
 *
 * @author Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, you may find one here:
 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
 * or you may search the http://www.gnu.org website for the version 2 license,
 * or you may write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 */

#include <connectivity/connectivity_items.h>

int CN_ITEM::AnchorCount() const
{
    if( !m_valid )
        return 0;

    switch( m_parent->Type() )
    {
    case PCB_PAD_T:
        return 5;  // center, north, south, east and west
    case PCB_TRACE_T:
    case PCB_ARC_T:
        return 2;  // start and end
    default:
        return 1;
    }
}


const VECTOR2I CN_ITEM::GetAnchor( int n ) const
{
    VECTOR2I pt0;

    if( !m_valid )
        return pt0;

    switch( m_parent->Type() )
    {
    case PCB_PAD_T:
    {
        D_PAD* pad = (D_PAD*) m_parent;

        if( n == 0 )
            return VECTOR2I( pad->GetPosition() );

        // ShapePos() is the geometric center (not anchor) for the pad
        pt0 = pad->ShapePos();
        VECTOR2I pt1 = pt0;

        switch( pad->GetShape() )
        {
        case PAD_SHAPE_RECT:
        case PAD_SHAPE_CIRCLE:
        case PAD_SHAPE_OVAL:
        case PAD_SHAPE_ROUNDRECT:
        case PAD_SHAPE_CHAMFERED_RECT:
            switch( n )
            {
            case 1: pt1.y -= pad->GetSize().y / 2; break;    // North
            case 2: pt1.y += pad->GetSize().y / 2; break;    // South
            case 3: pt1.x -= pad->GetSize().x / 2; break;    // East
            case 4: pt1.x += pad->GetSize().x / 2; break;    // West
            default:                               break;    // Wicked witch
            }

            if( pad->GetOrientation() )
                RotatePoint( pt1, pad->ShapePos(), pad->GetOrientation() );

            // Thermal spokes on circular pads form an 'X' instead of a '+'
            if( pad->GetShape() == PAD_SHAPE_CIRCLE )
                RotatePoint( pt1, pad->ShapePos(), 450 );

            return pt1;

        case PAD_SHAPE_TRAPEZOID:
        case PAD_SHAPE_CUSTOM:
        {
            switch( n )
            {
            case 1: pt1.y = INT_MIN / 2; break;    // North
            case 2: pt1.y = INT_MAX / 2; break;    // South
            case 3: pt1.x = INT_MIN / 2; break;    // East
            case 4: pt1.x = INT_MAX / 2; break;    // West
            default:                     break;    // Wicked witch
            }

            if( pad->GetOrientation() )
                RotatePoint( pt1, pad->ShapePos(), pad->GetOrientation() );

            SHAPE_POLY_SET padPolySet;
            pad->BuildPadShapePolygon( padPolySet, wxSize( 0, 0 ), ARC_LOW_DEF );
            const SHAPE_LINE_CHAIN& padOutline = padPolySet.COutline( 0 );
            SHAPE_LINE_CHAIN::INTERSECTIONS intersections;

            padOutline.Intersect( SEG( pt0, pt1 ), intersections );

            if( intersections.empty() )
            {
                // There should always be at least some copper outside the hole and/or
                // shapePos center
                assert( false );
                return pt0;
            }

            return intersections[ intersections.size() - 1 ].p;
        }
        }

        break;
    }
    case PCB_TRACE_T:
    case PCB_ARC_T:
        if( n == 0 )
            return static_cast<const TRACK*>( m_parent )->GetStart();
        else
            return static_cast<const TRACK*>( m_parent )->GetEnd();

    case PCB_VIA_T:
        return static_cast<const VIA*>( m_parent )->GetStart();

    default:
        assert( false );
        break;
    }

    return pt0;
}


int CN_ITEM::Net() const
{
    if( !m_parent || !m_valid )
        return -1;

    return m_parent->GetNetCode();
}


void CN_ITEM::Dump()
{
    printf("    valid: %d, connected: \n", !!Valid());

    for( auto i : m_connected )
    {
        TRACK* t = static_cast<TRACK*>( i->Parent() );
        printf( "    - %p %d\n", t, t->Type() );
    }
}


int CN_ZONE::AnchorCount() const
{
    if( !Valid() )
        return 0;

    const auto zone = static_cast<const ZONE_CONTAINER*>( Parent() );
    const auto& outline = zone->GetFilledPolysList().COutline( m_subpolyIndex );

    return outline.PointCount() ? 1 : 0;
}


const VECTOR2I CN_ZONE::GetAnchor( int n ) const
{
    if( !Valid() )
        return VECTOR2I();

    const auto zone = static_cast<const ZONE_CONTAINER*> ( Parent() );
    const auto& outline = zone->GetFilledPolysList().COutline( m_subpolyIndex );

    return outline.CPoint( 0 );
}


void CN_ITEM::RemoveInvalidRefs()
{
    for( auto it = m_connected.begin(); it != m_connected.end(); )
    {
        if( !(*it)->Valid() )
            it = m_connected.erase( it );
        else
            ++it;
    }
}


CN_ITEM* CN_LIST::Add( D_PAD* pad )
 {
    if( !pad->IsOnCopperLayer() )
         return nullptr;

     auto item = new CN_ITEM( pad, false, 1 );
     item->AddAnchor( pad->ShapePos() );
     item->SetLayers( LAYER_RANGE( F_Cu, B_Cu ) );

     switch( pad->GetAttribute() )
     {
     case PAD_ATTRIB_SMD:
     case PAD_ATTRIB_HOLE_NOT_PLATED:
     case PAD_ATTRIB_CONN:
     {
         LSET lmsk = pad->GetLayerSet();

         for( int i = 0; i <= MAX_CU_LAYERS; i++ )
         {
             if( lmsk[i] )
             {
                 item->SetLayer( i );
                 break;
             }
         }
         break;
     }
     default:
         break;
     }

     addItemtoTree( item );
     m_items.push_back( item );
     SetDirty();
     return item;
}

CN_ITEM* CN_LIST::Add( TRACK* track )
{
    auto item = new CN_ITEM( track, true );
    m_items.push_back( item );
    item->AddAnchor( track->GetStart() );
    item->AddAnchor( track->GetEnd() );
    item->SetLayer( track->GetLayer() );
    addItemtoTree( item );
    SetDirty();
    return item;
}

CN_ITEM* CN_LIST::Add( ARC* aArc )
{
    auto item = new CN_ITEM( aArc, true );
    m_items.push_back( item );
    item->AddAnchor( aArc->GetStart() );
    item->AddAnchor( aArc->GetEnd() );
    item->SetLayer( aArc->GetLayer() );
    addItemtoTree( item );
    SetDirty();
    return item;
}

 CN_ITEM* CN_LIST::Add( VIA* via )
 {
     auto item = new CN_ITEM( via, true, 1 );

     m_items.push_back( item );
     item->AddAnchor( via->GetStart() );
     item->SetLayers( LAYER_RANGE( F_Cu, B_Cu ) );
     addItemtoTree( item );
     SetDirty();
     return item;
 }

 const std::vector<CN_ITEM*> CN_LIST::Add( ZONE_CONTAINER* zone )
 {
     const auto& polys = zone->GetFilledPolysList();

     std::vector<CN_ITEM*> rv;

     for( int j = 0; j < polys.OutlineCount(); j++ )
     {
         CN_ZONE* zitem = new CN_ZONE( zone, false, j );
         const auto& outline = zone->GetFilledPolysList().COutline( j );

         for( int k = 0; k < outline.PointCount(); k++ )
             zitem->AddAnchor( outline.CPoint( k ) );

         m_items.push_back( zitem );
         zitem->SetLayer( zone->GetLayer() );
         addItemtoTree( zitem );
         rv.push_back( zitem );
         SetDirty();
     }

     return rv;
 }


void CN_LIST::RemoveInvalidItems( std::vector<CN_ITEM*>& aGarbage )
{
    if( !m_hasInvalid )
        return;

    auto lastItem = std::remove_if(m_items.begin(), m_items.end(), [&aGarbage] ( CN_ITEM* item )
    {
        if( !item->Valid() )
        {
            aGarbage.push_back ( item );
            return true;
        }

        return false;
    } );

    m_items.resize( lastItem - m_items.begin() );

    for( auto item : m_items )
        item->RemoveInvalidRefs();

    for( auto item : aGarbage )
        m_index.Remove( item );

    m_hasInvalid = false;
}


BOARD_CONNECTED_ITEM* CN_ANCHOR::Parent() const
{
    assert( m_item->Valid() );
    return m_item->Parent();
}


bool CN_ANCHOR::Valid() const
{
    if( !m_item )
        return false;

    return m_item->Valid();
}


bool CN_ANCHOR::IsDangling() const
{
    if( !m_cluster )
        return true;

    // the minimal number of items connected to item_ref
    // at this anchor point to decide the anchor is *not* dangling
    size_t minimal_count = 1;
    size_t connected_count = m_item->ConnectedItems().size();

    // a via can be removed if connected to only one other item.
    if( Parent()->Type() == PCB_VIA_T )
        return connected_count < 2;

    if( m_item->AnchorCount() == 1 )
        return connected_count < minimal_count;

    // Items with multiple anchors have usually items connected to each anchor.
    // We want only the item count of this anchor point
    connected_count = 0;
    for( auto item : m_item->ConnectedItems() )
    {
        if( item->Parent()->Type() == PCB_ZONE_AREA_T )
        {
            ZONE_CONTAINER* zone = static_cast<ZONE_CONTAINER*>( item->Parent() );

            if( zone->HitTestFilledArea( (wxPoint) Pos() ) )
                connected_count++;
        }
        else if( item->Parent()->HitTest( (wxPoint) Pos() ) )
            connected_count++;
    }

    return connected_count < minimal_count;
}


int CN_ANCHOR::ConnectedItemsCount() const
{
    if( !m_cluster )
        return 0;

    int connected_count = 0;

    for( auto item : m_item->ConnectedItems() )
    {
        if( item->Parent()->Type() == PCB_ZONE_AREA_T )
        {
            ZONE_CONTAINER* zone = static_cast<ZONE_CONTAINER*>( item->Parent() );

            if( zone->HitTestFilledArea( wxPoint( Pos().x, Pos().y ) ) )
                connected_count++;
        }
        else if( item->Parent()->HitTest( wxPoint( Pos().x, Pos().y ) ) )
            connected_count++;
    }

    return connected_count;
}


CN_CLUSTER::CN_CLUSTER()
{
    m_items.reserve( 64 );
    m_originPad = nullptr;
    m_originNet = -1;
    m_conflicting = false;
}


CN_CLUSTER::~CN_CLUSTER()
{
}


wxString CN_CLUSTER::OriginNetName() const
{
    if( !m_originPad || !m_originPad->Valid() )
        return "<none>";
    else
        return m_originPad->Parent()->GetNetname();
}


bool CN_CLUSTER::Contains( const CN_ITEM* aItem )
{
    return std::find( m_items.begin(), m_items.end(), aItem ) != m_items.end();
}


bool CN_CLUSTER::Contains( const BOARD_CONNECTED_ITEM* aItem )
{
    return std::find_if( m_items.begin(), m_items.end(), [ &aItem ] ( const CN_ITEM* item )
            { return item->Valid() && item->Parent() == aItem; } ) != m_items.end();
}


void CN_CLUSTER::Dump()
{
    for( auto item : m_items )
    {
        wxLogTrace( "CN", " - item : %p bitem : %p type : %d inet %s\n", item, item->Parent(),
                item->Parent()->Type(), (const char*) item->Parent()->GetNetname().c_str() );
        printf( "- item : %p bitem : %p type : %d inet %s\n", item, item->Parent(),
                        item->Parent()->Type(), (const char*) item->Parent()->GetNetname().c_str() );
        item->Dump();
    }
}


void CN_CLUSTER::Add( CN_ITEM* item )
{
    m_items.push_back( item );

    if( item->Net() <= 0 )
        return;

    if( m_originNet <= 0 )
    {
        m_originNet = item->Net();
    }

    if( item->Parent()->Type() == PCB_PAD_T )
    {
        if( !m_originPad )
        {
            m_originPad = item;
            m_originNet = item->Net();
        }

        if( m_originPad && item->Net() != m_originNet )
        {
            m_conflicting = true;
        }
    }
}
