/*
 * This program source code file is part of KiCad, a free EDA CAD application.
 *
 * Copyright (C) 2014-2015 CERN
 * @author Maciej Suminski <maciej.suminski@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 "module_tools.h"
#include "selection_tool.h"
#include "common_actions.h"
#include <tool/tool_manager.h>

#include <class_draw_panel_gal.h>
#include <view/view_controls.h>
#include <view/view_group.h>
#include <pcb_painter.h>
#include <origin_viewitem.h>

#include <kicad_plugin.h>
#include <pcbnew_id.h>
#include <collectors.h>
#include <confirm.h>
#include <dialogs/dialog_enum_pads.h>

#include <wxPcbStruct.h>
#include <class_board.h>
#include <class_module.h>
#include <class_edge_mod.h>

#include <boost/bind.hpp>
#include <boost/foreach.hpp>
#include <wx/defs.h>

MODULE_TOOLS::MODULE_TOOLS() :
    TOOL_INTERACTIVE( "pcbnew.ModuleEditor" ), m_view( NULL ), m_controls( NULL ),
    m_board( NULL ), m_frame( NULL )
{
    // Generate an origin marker at 0,0 which is used as an axis origin marker (0,0)
    m_axisOrigin = new KIGFX::ORIGIN_VIEWITEM( KIGFX::COLOR4D(0.0, 0.0, 0.8, 1.0),
                                               KIGFX::ORIGIN_VIEWITEM::CROSS,
                                               20000,
                                               VECTOR2D(0,0) );
    m_axisOrigin->SetDrawAtZero( true );
}


MODULE_TOOLS::~MODULE_TOOLS()
{
    delete m_axisOrigin;
}


void MODULE_TOOLS::Reset( RESET_REASON aReason )
{
    // Init variables used by every drawing tool
    m_view = getView();
    m_controls = getViewControls();
    m_board = getModel<BOARD>();
    m_frame = getEditFrame<PCB_EDIT_FRAME>();

    if( aReason == MODEL_RELOAD || aReason == GAL_SWITCH )
    {
        // Draw the axis origin if we're editing modules (essentially in the footprint editor)
        m_view->Remove( m_axisOrigin );
        m_view->Add( m_axisOrigin );
    }
}


bool MODULE_TOOLS::Init()
{
    // Find the selection tool, so they can cooperate
    SELECTION_TOOL* selectionTool = m_toolMgr->GetTool<SELECTION_TOOL>();

    if( !selectionTool )
    {
        DisplayError( NULL, wxT( "pcbnew.InteractiveSelection tool is not available" ) );
        return false;
    }

    selectionTool->GetMenu().AddItem( COMMON_ACTIONS::enumeratePads );

    return true;
}


int MODULE_TOOLS::PlacePad( const TOOL_EVENT& aEvent )
{
    m_frame->SetToolID( ID_MODEDIT_PAD_TOOL, wxCURSOR_PENCIL, _( "Add pads" ) );

    assert( m_board->m_Modules );

    D_PAD* pad = new D_PAD( m_board->m_Modules );
    m_frame->Import_Pad_Settings( pad, false );     // use the global settings for pad

    VECTOR2I cursorPos = m_controls->GetCursorPosition();
    pad->SetPosition( wxPoint( cursorPos.x, cursorPos.y ) );

    // Add a VIEW_GROUP that serves as a preview for the new item
    KIGFX::VIEW_GROUP preview( m_view );
    preview.Add( pad );
    m_view->Add( &preview );

    m_toolMgr->RunAction( COMMON_ACTIONS::selectionClear, true );
    m_controls->ShowCursor( true );
    m_controls->SetSnapping( true );

    Activate();

    // Main loop: keep receiving events
    while( OPT_TOOL_EVENT evt = Wait() )
    {
        cursorPos = m_controls->GetCursorPosition();

        if( evt->IsMotion() )
        {
            pad->SetPosition( wxPoint( cursorPos.x, cursorPos.y ) );
            preview.ViewUpdate();
        }

        else if( evt->Category() == TC_COMMAND )
        {
            if( evt->IsAction( &COMMON_ACTIONS::rotate ) )
            {
                pad->Rotate( pad->GetPosition(), m_frame->GetRotationAngle() );
                preview.ViewUpdate( KIGFX::VIEW_ITEM::GEOMETRY );
            }
            else if( evt->IsAction( &COMMON_ACTIONS::flip ) )
            {
                pad->Flip( pad->GetPosition() );
                preview.ViewUpdate( KIGFX::VIEW_ITEM::GEOMETRY );
            }
            else if( evt->IsCancel() || evt->IsActivate() )
            {
                preview.Clear();
                delete pad;
                break;
            }
        }

        else if( evt->IsClick( BUT_LEFT ) )
        {
            m_frame->OnModify();
            m_frame->SaveCopyInUndoList( m_board->m_Modules, UR_MODEDIT );

            m_board->m_Status_Pcb = 0;    // I have no clue why, but it is done in the legacy view
            pad->SetParent( m_board->m_Modules );
            m_board->m_Modules->SetLastEditTime();
            m_board->m_Modules->Pads().PushBack( pad );

            // Set the relative pad position
            // ( pad position for module orient, 0, and relative to the module position)
            pad->SetLocalCoord();

            // Take the next available pad number
            pad->IncrementPadName( true, true );

            // Handle the view aspect
            preview.Remove( pad );
            m_view->Add( pad );

            // Start placing next pad
            pad = new D_PAD( m_board->m_Modules );
            m_frame->Import_Pad_Settings( pad, false );
            pad->SetPosition( wxPoint( cursorPos.x, cursorPos.y ) );
            preview.Add( pad );
        }
    }

    m_controls->ShowCursor( false );
    m_controls->SetSnapping( false );
    m_controls->SetAutoPan( false );
    m_view->Remove( &preview );

    m_frame->SetToolID( ID_NO_TOOL_SELECTED, wxCURSOR_DEFAULT, wxEmptyString );

    return 0;
}


int MODULE_TOOLS::EnumeratePads( const TOOL_EVENT& aEvent )
{
    std::list<D_PAD*> pads;
    std::set<D_PAD*> allPads;

    if( !m_board->m_Modules || !m_board->m_Modules->Pads() )
        return 0;

    GENERAL_COLLECTOR collector;
    const KICAD_T types[] = { PCB_PAD_T, EOT };

    GENERAL_COLLECTORS_GUIDE guide = m_frame->GetCollectorsGuide();
    guide.SetIgnoreMTextsMarkedNoShow( true );
    guide.SetIgnoreMTextsOnBack( true );
    guide.SetIgnoreMTextsOnFront( true );
    guide.SetIgnoreModulesVals( true );
    guide.SetIgnoreModulesRefs( true );

    // Create a set containing all pads (to avoid double adding to the list)
    for( D_PAD* p = m_board->m_Modules->Pads(); p; p = p->Next() )
        allPads.insert( p );

    DIALOG_ENUM_PADS settingsDlg( m_frame );

    if( settingsDlg.ShowModal() == wxID_CANCEL )
        return 0;

    int padNumber = settingsDlg.GetStartNumber();
    wxString padPrefix = settingsDlg.GetPrefix();

    m_frame->DisplayToolMsg( _( "Hold left mouse button and move cursor over pads to enumerate them" ) );

    Activate();

    m_toolMgr->RunAction( COMMON_ACTIONS::selectionClear, true );
    m_controls->ShowCursor( true );
    VECTOR2I oldCursorPos = m_controls->GetCursorPosition();
    std::list<D_PAD*> selectedPads;

    while( OPT_TOOL_EVENT evt = Wait() )
    {
        if( evt->IsDrag( BUT_LEFT ) || evt->IsClick( BUT_LEFT ) )
        {
            selectedPads.clear();
            VECTOR2I cursorPos = m_controls->GetCursorPosition();

            if( evt->IsClick( BUT_LEFT ) )
            {
                oldCursorPos = m_controls->GetCursorPosition();
                collector.Empty();
                collector.Collect( m_board, types, wxPoint( cursorPos.x, cursorPos.y ), guide );

                for( int i = 0; i < collector.GetCount(); ++i )
                {
                    if( collector[i]->Type() == PCB_PAD_T )
                        selectedPads.push_back( static_cast<D_PAD*>( collector[i] ) );
                }
            }
            else //evt->IsDrag( BUT_LEFT )
            {
                // wxWidgets deliver mouse move events not frequently enough, resulting in skipping
                // pads if the user moves cursor too fast. To solve it, create a line that approximates
                // the mouse move and select items intersecting with the line.
                int distance = ( cursorPos - oldCursorPos ).EuclideanNorm();
                int segments = distance / 100000 + 1;
                const wxPoint LINE_STEP( ( cursorPos - oldCursorPos ).x / segments,
                                         ( cursorPos - oldCursorPos ).y / segments );

                collector.Empty();
                for( int j = 0; j < segments; ++j ) {
                    collector.Collect( m_board, types,
                                       wxPoint( oldCursorPos.x, oldCursorPos.y ) + j * LINE_STEP,
                                       guide );

                    for( int i = 0; i < collector.GetCount(); ++i )
                    {
                        if( collector[i]->Type() == PCB_PAD_T )
                            selectedPads.push_back( static_cast<D_PAD*>( collector[i] ) );
                    }
                }

                selectedPads.unique();
            }

            BOOST_FOREACH( D_PAD* pad, selectedPads )
            {
                std::set<D_PAD*>::iterator it = allPads.find( pad );

                // Add the pad to the list, if it was not selected previously..
                if( it != allPads.end() )
                {
                    allPads.erase( it );
                    pads.push_back( pad );
                    pad->SetSelected();
                }

                // ..or remove it from the list if it was clicked
                else if( evt->IsClick( BUT_LEFT ) )
                {
                    allPads.insert( pad );
                    pads.remove( pad );
                    pad->ClearSelected();
                }
            }

            oldCursorPos = cursorPos;
        }

        else if( ( evt->IsKeyPressed() && evt->KeyCode() == WXK_RETURN ) ||
                   evt->IsDblClick( BUT_LEFT ) )
        {
            // Accept changes
            m_frame->OnModify();
            m_frame->SaveCopyInUndoList( m_board->m_Modules, UR_MODEDIT );

            BOOST_FOREACH( D_PAD* pad, pads )
                pad->SetPadName( wxString::Format( wxT( "%s%d" ), padPrefix.c_str(), padNumber++ ) );

            break;
        }

        else if( evt->IsCancel() || evt->IsActivate() )
        {
            break;
        }
    }

    BOOST_FOREACH( D_PAD* pad, pads )
        pad->ClearSelected();

    m_frame->DisplayToolMsg( wxEmptyString );
    m_controls->ShowCursor( false );

    return 0;
}


int MODULE_TOOLS::CopyItems( const TOOL_EVENT& aEvent )
{
    const SELECTION& selection = m_toolMgr->GetTool<SELECTION_TOOL>()->GetSelection();

    Activate();

    m_controls->SetSnapping( true );
    m_controls->ShowCursor( true );
    m_controls->SetAutoPan( true );

    m_frame->DisplayToolMsg( _( "Select reference point" ) );

    bool cancelled = false;
    VECTOR2I cursorPos = m_controls->GetCursorPosition();

    while( OPT_TOOL_EVENT evt = Wait() )
    {
        if( evt->IsMotion() )
        {
            cursorPos = m_controls->GetCursorPosition();
        }
        else if( evt->IsClick( BUT_LEFT ) )
        {
            break;
        }
        else if( evt->IsCancel() || evt->IsActivate() )
        {
            cancelled = true;
            break;
        }
    }

    if( !cancelled )
    {
        PCB_IO io( CTL_FOR_CLIPBOARD );

        // Create a temporary module that contains selected items to ease serialization
        MODULE module( m_board );

        for( int i = 0; i < selection.Size(); ++i )
        {
            BOARD_ITEM* clone = static_cast<BOARD_ITEM*>( selection.Item<BOARD_ITEM>( i )->Clone() );

            // Do not add reference/value - convert them to the common type
            if( TEXTE_MODULE* text = dyn_cast<TEXTE_MODULE*>( clone ) )
                text->SetType( TEXTE_MODULE::TEXT_is_DIVERS );

            module.Add( clone );
        }

        // Set the new relative internal local coordinates of copied items
        MODULE* editedModule = m_board->m_Modules;
        wxPoint moveVector = module.GetPosition() + editedModule->GetPosition() -
                             wxPoint( cursorPos.x, cursorPos.y );
        module.MoveAnchorPosition( moveVector );

        io.Format( &module, 0 );
        std::string data = io.GetStringOutput( true );
        m_toolMgr->SaveClipboard( data );
    }

    m_frame->DisplayToolMsg( wxString::Format( _( "Copied %d item(s)" ), selection.Size() ) );
    m_controls->SetSnapping( false );
    m_controls->ShowCursor( false );
    m_controls->SetAutoPan( false );

    return 0;
}


int MODULE_TOOLS::PasteItems( const TOOL_EVENT& aEvent )
{
    // Parse clipboard
    PCB_IO io( CTL_FOR_CLIPBOARD );
    MODULE* currentModule = m_board->m_Modules;
    MODULE* pastedModule = NULL;

    try
    {
        BOARD_ITEM* item = io.Parse( wxString( m_toolMgr->GetClipboard().c_str(), wxConvUTF8 ) );
        assert( item->Type() == PCB_MODULE_T );
        pastedModule = dyn_cast<MODULE*>( item );
    }
    catch( ... )
    {
        m_frame->DisplayToolMsg( _( "Invalid clipboard contents" ) );
        return 0;
    }

    // Placement tool part
    VECTOR2I cursorPos = m_controls->GetCursorPosition();

    // Add a VIEW_GROUP that serves as a preview for the new item
    KIGFX::VIEW_GROUP preview( m_view );
    pastedModule->SetParent( m_board );
    pastedModule->SetPosition( wxPoint( cursorPos.x, cursorPos.y ) );
    pastedModule->RunOnChildren( boost::bind( &KIGFX::VIEW_GROUP::Add, boost::ref( preview ), _1 ) );
    preview.Add( pastedModule );
    m_view->Add( &preview );

    m_toolMgr->RunAction( COMMON_ACTIONS::selectionClear, true );
    m_controls->ShowCursor( true );
    m_controls->SetSnapping( true );
    m_controls->SetAutoPan( true );

    Activate();

    // Main loop: keep receiving events
    while( OPT_TOOL_EVENT evt = Wait() )
    {
        cursorPos = m_controls->GetCursorPosition();

        if( evt->IsMotion() )
        {
            pastedModule->SetPosition( wxPoint( cursorPos.x, cursorPos.y ) );
            preview.ViewUpdate();
        }

        else if( evt->Category() == TC_COMMAND )
        {
            if( evt->IsAction( &COMMON_ACTIONS::rotate ) )
            {
                pastedModule->Rotate( pastedModule->GetPosition(), m_frame->GetRotationAngle() );
                preview.ViewUpdate( KIGFX::VIEW_ITEM::GEOMETRY );
            }
            else if( evt->IsAction( &COMMON_ACTIONS::flip ) )
            {
                pastedModule->Flip( pastedModule->GetPosition() );
                preview.ViewUpdate( KIGFX::VIEW_ITEM::GEOMETRY );
            }
            else if( evt->IsCancel() || evt->IsActivate() )
            {
                preview.Clear();
                break;
            }
        }

        else if( evt->IsClick( BUT_LEFT ) )
        {
            m_frame->OnModify();
            m_frame->SaveCopyInUndoList( currentModule, UR_MODEDIT );

            m_board->m_Status_Pcb = 0;    // I have no clue why, but it is done in the legacy view
            currentModule->SetLastEditTime();

            // MODULE::RunOnChildren is infeasible here: we need to create copies of items, do not
            // directly modify them

            for( D_PAD* pad = pastedModule->Pads(); pad; pad = pad->Next() )
            {
                D_PAD* clone = static_cast<D_PAD*>( pad->Clone() );

                currentModule->Add( clone );
                clone->SetLocalCoord();
                m_view->Add( clone );
            }

            for( BOARD_ITEM* drawing = pastedModule->GraphicalItems();
                    drawing; drawing = drawing->Next() )
            {
                BOARD_ITEM* clone = static_cast<BOARD_ITEM*>( drawing->Clone() );

                if( TEXTE_MODULE* text = dyn_cast<TEXTE_MODULE*>( clone ) )
                {
                    // Do not add reference/value - convert them to the common type
                    text->SetType( TEXTE_MODULE::TEXT_is_DIVERS );
                    currentModule->Add( text );
                    text->SetLocalCoord();

                    // Whyyyyyyyyyyyyyyyyyyyyyy?! All other items conform to rotation performed
                    // on its parent module, but texts are so independent..
                    text->Rotate( text->GetPosition(), pastedModule->GetOrientation() );
                }
                else if( EDGE_MODULE* edge = dyn_cast<EDGE_MODULE*>( clone ) )
                {
                    currentModule->Add( edge );
                    edge->SetLocalCoord();
                }

                m_view->Add( clone );
            }

            preview.Clear();

            break;
        }
    }

    delete pastedModule;
    m_controls->ShowCursor( false );
    m_controls->SetSnapping( false );
    m_controls->SetAutoPan( false );
    m_view->Remove( &preview );

    return 0;
}


int MODULE_TOOLS::ModuleTextOutlines( const TOOL_EVENT& aEvent )
{
    KIGFX::PCB_PAINTER* painter =
            static_cast<KIGFX::PCB_PAINTER*>( m_frame->GetGalCanvas()->GetView()->GetPainter() );
    KIGFX::PCB_RENDER_SETTINGS* settings =
            static_cast<KIGFX::PCB_RENDER_SETTINGS*>( painter->GetSettings() );

    const LAYER_NUM layers[] = { ITEM_GAL_LAYER( MOD_TEXT_BK_VISIBLE ),
                                 ITEM_GAL_LAYER( MOD_TEXT_FR_VISIBLE ),
                                 ITEM_GAL_LAYER( MOD_TEXT_INVISIBLE ),
                                 ITEM_GAL_LAYER( MOD_REFERENCES_VISIBLE ),
                                 ITEM_GAL_LAYER( MOD_VALUES_VISIBLE ) };

    bool enable = !settings->GetSketchMode( layers[0] );

    BOOST_FOREACH( LAYER_NUM layer, layers )
        settings->SetSketchMode( layer, enable );

    for( MODULE* module = getModel<BOARD>()->m_Modules; module; module = module->Next() )
    {
        for( BOARD_ITEM* item = module->GraphicalItems(); item; item = item ->Next() )
        {
            if( item->Type() == PCB_MODULE_TEXT_T )
                item->ViewUpdate( KIGFX::VIEW_ITEM::GEOMETRY );
        }

        module->Reference().ViewUpdate( KIGFX::VIEW_ITEM::GEOMETRY );
        module->Value().ViewUpdate( KIGFX::VIEW_ITEM::GEOMETRY );
    }

    m_frame->GetGalCanvas()->Refresh();

    return 0;
}


int MODULE_TOOLS::ModuleEdgeOutlines( const TOOL_EVENT& aEvent )
{
    KIGFX::PCB_PAINTER* painter =
            static_cast<KIGFX::PCB_PAINTER*>( m_frame->GetGalCanvas()->GetView()->GetPainter() );
    KIGFX::PCB_RENDER_SETTINGS* settings =
            static_cast<KIGFX::PCB_RENDER_SETTINGS*>( painter->GetSettings() );

    const LAYER_ID layers[] = { F_Adhes, B_Adhes, F_Paste, B_Paste,
            F_SilkS, B_SilkS, F_Mask, B_Mask,
            Dwgs_User, Cmts_User, Eco1_User, Eco2_User, Edge_Cuts };

    bool enable = !settings->GetSketchMode( layers[0] );

    BOOST_FOREACH( LAYER_NUM layer, layers )
        settings->SetSketchMode( layer, enable );

    for( MODULE* module = getModel<BOARD>()->m_Modules; module; module = module->Next() )
    {
        for( BOARD_ITEM* item = module->GraphicalItems(); item; item = item ->Next() )
        {
            if( item->Type() == PCB_MODULE_EDGE_T )
                item->ViewUpdate( KIGFX::VIEW_ITEM::GEOMETRY );
        }
    }

    m_frame->GetGalCanvas()->Refresh();

    return 0;
}


void MODULE_TOOLS::SetTransitions()
{
    Go( &MODULE_TOOLS::PlacePad,            COMMON_ACTIONS::placePad.MakeEvent() );
    Go( &MODULE_TOOLS::EnumeratePads,       COMMON_ACTIONS::enumeratePads.MakeEvent() );
    Go( &MODULE_TOOLS::CopyItems,           COMMON_ACTIONS::copyItems.MakeEvent() );
    Go( &MODULE_TOOLS::PasteItems,          COMMON_ACTIONS::pasteItems.MakeEvent() );
    Go( &MODULE_TOOLS::ModuleTextOutlines,  COMMON_ACTIONS::moduleTextOutlines.MakeEvent() );
    Go( &MODULE_TOOLS::ModuleEdgeOutlines,  COMMON_ACTIONS::moduleEdgeOutlines.MakeEvent() );
}
