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

#include <tools/zone_create_helper.h>

#include <view/view.h>
#include <tool/tool_manager.h>
#include <class_zone.h>
#include <class_drawsegment.h>
#include <class_edge_mod.h>
#include <class_pad.h>
#include <board_commit.h>
#include <pcb_painter.h>

#include <tools/pcb_actions.h>
#include <tools/selection_tool.h>

#include <zone_filler.h>

ZONE_CREATE_HELPER::ZONE_CREATE_HELPER( DRAWING_TOOL& aTool,
                   const PARAMS& aParams ):
        m_tool( aTool ),
        m_params( aParams ),
        m_parentView( *aTool.getView() )
{
    m_parentView.Add( &m_previewItem );
}


ZONE_CREATE_HELPER::~ZONE_CREATE_HELPER()
{
    // remove the preview from the view
    m_parentView.SetVisible( &m_previewItem, false );
    m_parentView.Remove( &m_previewItem );
}


std::unique_ptr<ZONE_CONTAINER> ZONE_CREATE_HELPER::createNewZone( bool aKeepout )
{
    auto& frame = *m_tool.getEditFrame<PCB_BASE_EDIT_FRAME>();
    auto& board = *m_tool.getModel<BOARD>();

    // Get the current default settings for zones
    ZONE_SETTINGS zoneInfo = frame.GetZoneSettings();
    zoneInfo.m_CurrentZone_Layer = frame.GetScreen()->m_Active_Layer;
    zoneInfo.m_NetcodeSelection = board.GetHighLightNetCode();
    zoneInfo.SetIsKeepout( m_params.m_keepout );

    if( m_params.m_mode != DRAWING_TOOL::ZONE_MODE::GRAPHIC_POLYGON )
    {
        // Get the current default settings for zones

        // Show options dialog
        ZONE_EDIT_T dialogResult;

        if( m_params.m_keepout )
            dialogResult = InvokeKeepoutAreaEditor( &frame, &zoneInfo );
        else
        {
            if( IsCopperLayer( zoneInfo.m_CurrentZone_Layer ) )
                dialogResult = InvokeCopperZonesEditor( &frame, &zoneInfo );
            else
                dialogResult = InvokeNonCopperZonesEditor( &frame, nullptr, &zoneInfo );
        }

        if( dialogResult == ZONE_ABORT )
        {
            return nullptr;
        }
    }

    auto newZone = std::make_unique<ZONE_CONTAINER>( &board );

    // Apply the selected settings
    zoneInfo.ExportSetting( *newZone );

    return newZone;
}


std::unique_ptr<ZONE_CONTAINER> ZONE_CREATE_HELPER::createZoneFromExisting(
        const ZONE_CONTAINER& aSrcZone )
{
    auto& board = *m_tool.getModel<BOARD>();

    auto newZone = std::make_unique<ZONE_CONTAINER>( &board );

    ZONE_SETTINGS zoneSettings;
    zoneSettings << aSrcZone;

    zoneSettings.ExportSetting( *newZone );

    return newZone;
}


void ZONE_CREATE_HELPER::performZoneCutout( ZONE_CONTAINER& aExistingZone,
                                            ZONE_CONTAINER& aCutout )
{
    BOARD* board = m_tool.getModel<BOARD>();
    int curr_hole = aExistingZone.Outline()->NewHole( 0 );

    // Copy cutout corners into existing zone, in the new hole
    for( int ii = 0; ii < aCutout.GetNumCorners(); ii++ )
    {
        aExistingZone.Outline()->Append( aCutout.GetCornerPosition( ii ), 0, curr_hole );
    }

    // Be sure the current corner list is closed
    aExistingZone.Outline()->Hole( 0, curr_hole ).SetClosed( true );

    // Combine holes and simplify the new outline:
    board->OnAreaPolygonModified( nullptr, &aExistingZone );

    // Re-fill if needed
    if( aExistingZone.IsFilled() )
    {
        ZONE_FILLER filler( board );
        filler.Fill( { &aExistingZone } );
    }
}


void ZONE_CREATE_HELPER::commitZone( std::unique_ptr<ZONE_CONTAINER> aZone )
{
    auto& frame = *m_tool.getEditFrame<PCB_EDIT_FRAME>();
    auto board = m_tool.getModel<BOARD>();

    BOARD_COMMIT bCommit( &m_tool );

    switch ( m_params.m_mode )
    {
        case DRAWING_TOOL::ZONE_MODE::CUTOUT:
                // For cutouts, subtract from the source
            bCommit.Modify( m_params.m_sourceZone );
            performZoneCutout( *m_params.m_sourceZone, *aZone );
            bCommit.Push( _( "Add a zone cutout" ) );
            m_params.m_sourceZone->Hatch();
            break;

        case DRAWING_TOOL::ZONE_MODE::ADD:
        case DRAWING_TOOL::ZONE_MODE::SIMILAR:
            aZone->Hatch();

            if( !m_params.m_keepout )
            {
                ZONE_FILLER filler( board );
                filler.Fill( { aZone.get() } );
            }

            bCommit.Add( aZone.release() );
            bCommit.Push( _( "Add a zone" ) );
            break;

        case DRAWING_TOOL::ZONE_MODE::GRAPHIC_POLYGON:
        {
            BOARD_ITEM_CONTAINER* parent = frame.GetModel();
            auto poly = m_tool.m_editModules ? new EDGE_MODULE( (MODULE *) parent ) : new DRAWSEGMENT();

            poly->SetShape ( S_POLYGON );
            poly->SetLayer( m_tool.getDrawingLayer() );
            poly->SetPolyShape ( *aZone->Outline() );

            bCommit.Add( poly );
            bCommit.Push( _( "Add a graphical polygon" ) );

            break;
        }
    }
}


bool ZONE_CREATE_HELPER::OnFirstPoint( POLYGON_GEOM_MANAGER& aMgr )
{
    // if we don't have a zone, create one
    // the user's choice here can affect things like the colour
    // of the preview
    if( !m_zone )
    {
        if( m_params.m_sourceZone )
            m_zone = createZoneFromExisting( *m_params.m_sourceZone );
        else
            m_zone = createNewZone( m_params.m_keepout );

        if( m_zone )
        {
            // set up poperties from zone
            const auto& settings = *m_parentView.GetPainter()->GetSettings();
            COLOR4D color = settings.GetColor( nullptr, m_zone->GetLayer() );

            m_previewItem.SetStrokeColor( color );
            m_previewItem.SetFillColor( color.WithAlpha( 0.2 ) );

            m_parentView.SetVisible( &m_previewItem, true );

            aMgr.SetLeaderMode( m_zone.get()->GetHV45()
                                ? POLYGON_GEOM_MANAGER::LEADER_MODE::DEG45
                                : POLYGON_GEOM_MANAGER::LEADER_MODE::DIRECT );
        }
    }

    if( !m_zone )
    {
        return false;
    }

    return true;
}


void ZONE_CREATE_HELPER::OnGeometryChange( const POLYGON_GEOM_MANAGER& aMgr )
{
    // send the points to the preview item
    m_previewItem.SetPoints( aMgr.GetLockedInPoints(), aMgr.GetLeaderLinePoints() );
    m_parentView.Update( &m_previewItem, KIGFX::GEOMETRY );
}


void ZONE_CREATE_HELPER::OnComplete( const POLYGON_GEOM_MANAGER& aMgr )
{
    auto& finalPoints = aMgr.GetLockedInPoints();

    if( finalPoints.PointCount() < 3 )
    {
        // just scrap the zone in progress
        m_zone = nullptr;
    }
    else
    {
        // if m_params.m_mode == DRAWING_TOOL::ZONE_MODE::CUTOUT, m_zone
        // will be merged to the existing zone as a new hole.
        m_zone->Outline()->NewOutline();
        auto* outline = m_zone->Outline();

        for( int i = 0; i < finalPoints.PointCount(); ++i )
        {
            outline->Append( finalPoints.CPoint( i ) );
        }

        outline->Outline( 0 ).SetClosed( true );
        outline->RemoveNullSegments();

        // hand the zone over to the committer
        commitZone( std::move( m_zone ) );
    }

    m_parentView.SetVisible( &m_previewItem, false );
}