#include "pcb_test_frame.h"
#include "pcb_test_selection_tool.h"

#include <pcbnew/tools/pcb_actions.h>
#include <tool/actions.h>
#include <tool/tool_manager.h>

PCB_TEST_SELECTION_TOOL::PCB_TEST_SELECTION_TOOL()
    : SELECTION_TOOL( "pcbnew.InteractiveSelection" )
{
}

PCB_TEST_SELECTION_TOOL::~PCB_TEST_SELECTION_TOOL()
{
    view()->Remove( &m_selection );
}

bool PCB_TEST_SELECTION_TOOL::Init()
{
    view()->Remove( &m_selection );
    view()->Add( &m_selection );

    return true;
}

void PCB_TEST_SELECTION_TOOL::Reset( RESET_REASON aReason )
{
}


void PCB_TEST_SELECTION_TOOL::ClearSelection()
{
    if( m_selection.Empty() )
        return;

    while( m_selection.GetSize() )
        unhighlight( m_selection.Front(), SELECTED, &m_selection );

    view()->Update( &m_selection );

    m_selection.SetIsHover( false );
    m_selection.ClearReferencePoint();
}

#include <collectors.h>


const GENERAL_COLLECTORS_GUIDE PCB_TEST_SELECTION_TOOL::getCollectorsGuide() const
{
    GENERAL_COLLECTORS_GUIDE guide( board()->GetVisibleLayers(),
                                    (PCB_LAYER_ID) view()->GetTopLayer(), view() );

    bool padsDisabled = !board()->IsElementVisible( LAYER_PADS );

    // account for the globals
    guide.SetIgnoreFPTextOnBack( !board()->IsElementVisible( LAYER_FP_TEXT ) );
    guide.SetIgnoreFPTextOnFront( !board()->IsElementVisible( LAYER_FP_TEXT ) );
    guide.SetIgnoreFootprintsOnBack( !board()->IsElementVisible( LAYER_FOOTPRINTS_BK ) );
    guide.SetIgnoreFootprintsOnFront( !board()->IsElementVisible( LAYER_FOOTPRINTS_FR ) );
    guide.SetIgnorePadsOnBack( padsDisabled );
    guide.SetIgnorePadsOnFront( padsDisabled );
    guide.SetIgnoreThroughHolePads( padsDisabled );
    guide.SetIgnoreFPValues( !board()->IsElementVisible( LAYER_FP_VALUES ) );
    guide.SetIgnoreFPReferences( !board()->IsElementVisible( LAYER_FP_REFERENCES ) );
    guide.SetIgnoreThroughVias( !board()->IsElementVisible( LAYER_VIAS ) );
    guide.SetIgnoreBlindBuriedVias( !board()->IsElementVisible( LAYER_VIAS ) );
    guide.SetIgnoreMicroVias( !board()->IsElementVisible( LAYER_VIAS ) );
    guide.SetIgnoreTracks( !board()->IsElementVisible( LAYER_TRACKS ) );

    return guide;
}

bool PCB_TEST_SELECTION_TOOL::selectPoint( const VECTOR2I& aWhere )
{
    GENERAL_COLLECTORS_GUIDE guide = getCollectorsGuide();
    GENERAL_COLLECTOR        collector;

    for( auto item : m_selection.Items() )
    {
        unhighlight( item, SELECTED, &m_selection );
    }

    m_selection.Clear();
    m_selection.ClearReferencePoint();

    if( m_selectableTypes.empty() )
        collector.Collect( board(), GENERAL_COLLECTOR::AllBoardItems, aWhere, guide );
    else
        collector.Collect( board(), m_selectableTypes, aWhere, guide );

    if( collector.GetCount() > 0 )
    {
        for( int i = 0; i < collector.GetCount(); ++i )
        {
            {
                select( collector[i] );
            }
        }
    }

    m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );

    if( m_selectionHook )
        m_selectionHook( frame(), &m_selection );

    return false;
}


void PCB_TEST_SELECTION_TOOL::setTransitions()
{
    Go( &PCB_TEST_SELECTION_TOOL::Main, PCB_ACTIONS::selectionActivate.MakeEvent() );
}


void PCB_TEST_SELECTION_TOOL::highlightInternal( EDA_ITEM* aItem, int aMode, bool aUsingOverlay )
{
    if( aMode == SELECTED )
        aItem->SetSelected();
    else if( aMode == BRIGHTENED )
        aItem->SetBrightened();

    if( aUsingOverlay && aMode != BRIGHTENED )
        view()->Hide( aItem, true ); // Hide the original item, so it is shown only on overlay

    if( aItem->IsBOARD_ITEM() )
    {
        BOARD_ITEM* boardItem = static_cast<BOARD_ITEM*>( aItem );
        boardItem->RunOnDescendants( std::bind( &PCB_TEST_SELECTION_TOOL::highlightInternal, this,
                                                std::placeholders::_1, aMode, aUsingOverlay ) );
    }
}


void PCB_TEST_SELECTION_TOOL::unhighlight( EDA_ITEM* aItem, int aMode, SELECTION* aGroup )
{
    if( aGroup )
        aGroup->Remove( aItem );

    unhighlightInternal( aItem, aMode, aGroup != nullptr );
    view()->Update( aItem, KIGFX::REPAINT );

    // Many selections are very temporal and updating the display each time just creates noise.
    if( aMode == BRIGHTENED )
        getView()->MarkTargetDirty( KIGFX::TARGET_OVERLAY );
}


void PCB_TEST_SELECTION_TOOL::unhighlightInternal( EDA_ITEM* aItem, int aMode, bool aUsingOverlay )
{
    if( aMode == SELECTED )
        aItem->ClearSelected();
    else if( aMode == BRIGHTENED )
        aItem->ClearBrightened();

    if( aUsingOverlay && aMode != BRIGHTENED )
    {
        view()->Hide( aItem, false ); // Restore original item visibility...
        view()->Update( aItem );      // ... and make sure it's redrawn un-selected
    }

    if( aItem->IsBOARD_ITEM() )
    {
        BOARD_ITEM* boardItem = static_cast<BOARD_ITEM*>( aItem );
        boardItem->RunOnDescendants( std::bind( &PCB_TEST_SELECTION_TOOL::unhighlightInternal, this,
                                                std::placeholders::_1, aMode, aUsingOverlay ) );
    }
}


void PCB_TEST_SELECTION_TOOL::highlight( EDA_ITEM* aItem, int aMode, SELECTION* aGroup )
{
    if( aGroup )
        aGroup->Add( aItem );

    highlightInternal( aItem, aMode, aGroup != nullptr );
    view()->Update( aItem, KIGFX::REPAINT );
}


void PCB_TEST_SELECTION_TOOL::select( EDA_ITEM* aItem )
{
    if( aItem->IsSelected() )
        return;

    highlight( aItem, SELECTED, &m_selection );
}


void PCB_TEST_SELECTION_TOOL::unselect( EDA_ITEM* aItem )
{
    unhighlight( aItem, SELECTED, &m_selection );
}

int PCB_TEST_SELECTION_TOOL::Main( const TOOL_EVENT& aEvent )
{
    // Main loop: keep receiving events
    while( TOOL_EVENT* evt = Wait() )
    {
        if( evt->IsClick( BUT_LEFT ) )
        {
            selectPoint( evt->Position() );
        }
        else if( evt->IsCancel() )
        {
            if( !GetSelection().Empty() )
            {
                ClearSelection();
            }
        }
        else
        {
            evt->SetPassEvent();
        }
    }

    // Shutting down; clear the selection
    m_selection.Clear();
    m_disambiguateTimer.Stop();

    return 0;
}

void PCB_TEST_SELECTION_TOOL::SetSelectableItemTypes( const std::vector<KICAD_T> aTypes )
{
    m_selectableTypes = aTypes;
}
