/*
 * This program source code file is part of KiCad, a free EDA CAD application.
 *
 * Copyright (C) 2009 Jean-Pierre Charras, jean-pierre.charras@ujf-grenoble.fr
 * Copyright (C) 1992-2023 KiCad Developers, see AUTHORS.txt for contributors.
 * Copyright (C) 2018 CERN
 * Author: Maciej Suminski <maciej.suminski@cern.ch>
 * 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 "pcbnew_printout.h"
#include <board.h>
#include <math/util.h>      // for KiROUND
#include <gal/graphics_abstraction_layer.h>
#include <pcb_painter.h>
#include <pcbnew_settings.h>
#include <view/view.h>
#include <pcbplot.h>
#include <geometry/shape_segment.h>
#include <pad.h>

#include <advanced_config.h>

PCBNEW_PRINTOUT_SETTINGS::PCBNEW_PRINTOUT_SETTINGS( const PAGE_INFO& aPageInfo ) :
        BOARD_PRINTOUT_SETTINGS( aPageInfo )
{
    m_DrillMarks = DRILL_MARKS::SMALL_DRILL_SHAPE;
    m_Pagination = ALL_LAYERS;
    m_PrintEdgeCutsOnAllPages = true;
    m_AsItemCheckboxes = false;
}


void PCBNEW_PRINTOUT_SETTINGS::Load( APP_SETTINGS_BASE* aConfig )
{
    BOARD_PRINTOUT_SETTINGS::Load( aConfig );

    if( PCBNEW_SETTINGS* cfg = dynamic_cast<PCBNEW_SETTINGS*>( aConfig ) )
    {
        m_DrillMarks = static_cast<DRILL_MARKS>( cfg->m_Plot.pads_drill_mode );
        m_Pagination = static_cast<PAGINATION_T>( cfg->m_Plot.all_layers_on_one_page );
        m_PrintEdgeCutsOnAllPages = cfg->m_Plot.edgecut_on_all_layers;
        m_Mirror                  = cfg->m_Plot.mirror;
        m_AsItemCheckboxes        = cfg->m_Plot.as_item_checkboxes;
    }
}


void PCBNEW_PRINTOUT_SETTINGS::Save( APP_SETTINGS_BASE* aConfig )
{
    BOARD_PRINTOUT_SETTINGS::Save( aConfig );

    if( PCBNEW_SETTINGS* cfg = dynamic_cast<PCBNEW_SETTINGS*>( aConfig ) )
    {
        cfg->m_Plot.pads_drill_mode        = (int)m_DrillMarks;
        cfg->m_Plot.all_layers_on_one_page = m_Pagination;
        cfg->m_Plot.edgecut_on_all_layers  = m_PrintEdgeCutsOnAllPages;
        cfg->m_Plot.mirror                 = m_Mirror;
        cfg->m_Plot.as_item_checkboxes     = m_AsItemCheckboxes;
    }
}


PCBNEW_PRINTOUT::PCBNEW_PRINTOUT( BOARD* aBoard, const PCBNEW_PRINTOUT_SETTINGS& aParams,
                                  const KIGFX::VIEW* aView, const wxString& aTitle ) :
        BOARD_PRINTOUT( aParams, aView, aTitle ), m_pcbnewSettings( aParams )
{
    m_board = aBoard;
}


bool PCBNEW_PRINTOUT::OnPrintPage( int aPage )
{
    // Store the layerset, as it is going to be modified below and the original settings are
    // needed.
    LSET         lset = m_settings.m_LayerSet;
    int          pageCount = lset.count();
    wxString     layerName;
    PCB_LAYER_ID extractLayer;

    // compute layer mask from page number if we want one page per layer
    if( m_pcbnewSettings.m_Pagination == PCBNEW_PRINTOUT_SETTINGS::LAYER_PER_PAGE )
    {
        // This sequence is TBD, call a different sequencer if needed, such as Seq().
        // Could not find documentation on page order.
        LSEQ seq = lset.UIOrder();

        // aPage starts at 1, not 0
        if( unsigned( aPage - 1 ) < seq.size() )
            m_settings.m_LayerSet = LSET( seq[ aPage - 1] );
    }

    if( !m_settings.m_LayerSet.any() )
        return false;

    extractLayer = m_settings.m_LayerSet.ExtractLayer();

    if( extractLayer == UNDEFINED_LAYER )
        layerName = _( "Multiple Layers" );
    else
        layerName = m_board->GetLayerName( extractLayer );

    // In Pcbnew we can want the layer EDGE always printed
    if( m_pcbnewSettings.m_PrintEdgeCutsOnAllPages )
        m_settings.m_LayerSet.set( Edge_Cuts );

    DrawPage( layerName, aPage, pageCount );

    // Restore the original layer set, so the next page can be printed
    m_settings.m_LayerSet = lset;

    return true;
}


int PCBNEW_PRINTOUT::milsToIU( double aMils ) const
{
    return KiROUND( pcbIUScale.IU_PER_MILS * aMils );
}


void PCBNEW_PRINTOUT::setupViewLayers( KIGFX::VIEW& aView, const LSET& aLayerSet )
{
    BOARD_PRINTOUT::setupViewLayers( aView, aLayerSet );

    for( PCB_LAYER_ID layer : m_settings.m_LayerSet.Seq() )
    {
        aView.SetLayerVisible( PCBNEW_LAYER_ID_START + layer, true );

        // Enable the corresponding zone layer (copper layers and other layers)
        aView.SetLayerVisible( LAYER_ZONE_START + layer, true );
    }

    RENDER_SETTINGS* renderSettings = aView.GetPainter()->GetSettings();
    // A color to do not print objects on some layers, when the layer must be enabled
    // to print some other objects
    COLOR4D invisible_color = COLOR4D::UNSPECIFIED;

    if( m_pcbnewSettings.m_AsItemCheckboxes )
    {
        auto setVisibility =
                [&]( GAL_LAYER_ID aLayer )
                {
                    if( m_board->IsElementVisible( aLayer ) )
                        aView.SetLayerVisible( aLayer, true );
                    else
                        renderSettings->SetLayerColor( aLayer, invisible_color );
                };

        setVisibility( LAYER_FOOTPRINTS_FR );
        setVisibility( LAYER_FOOTPRINTS_BK );
        setVisibility( LAYER_FP_VALUES );
        setVisibility( LAYER_FP_REFERENCES );
        setVisibility( LAYER_FP_TEXT );
        setVisibility( LAYER_HIDDEN_TEXT );
        setVisibility( LAYER_PADS );
        setVisibility( LAYER_PADS_SMD_FR );
        setVisibility( LAYER_PADS_SMD_BK );
        setVisibility( LAYER_PADS_TH );

        setVisibility( LAYER_TRACKS );
        setVisibility( LAYER_VIAS );
        setVisibility( LAYER_VIA_MICROVIA );
        setVisibility( LAYER_VIA_BBLIND );
        setVisibility( LAYER_VIA_THROUGH );
        setVisibility( LAYER_ZONES );

        setVisibility( LAYER_DRC_WARNING );
        setVisibility( LAYER_DRC_ERROR );
        setVisibility( LAYER_DRC_EXCLUSION );
        setVisibility( LAYER_ANCHOR );
        setVisibility( LAYER_DRAWINGSHEET );
        setVisibility( LAYER_GRID );
    }
    else
    {
        // Draw layers that must be not visible on printing are set to an invisible layer
        // LAYER_PADS_SMD_FR, LAYER_PADS_SMD_BK and LAYER_PADS_TH must be enabled to print pads on
        // technical layers, but not the pad on copper layer(s) if they are not enabled

        if( !aLayerSet.test( F_Cu ) )
            renderSettings->SetLayerColor( LAYER_PADS_SMD_FR, invisible_color );

        if( !aLayerSet.test( B_Cu ) )
            renderSettings->SetLayerColor( LAYER_PADS_SMD_BK, invisible_color );

        // Enable items on copper layers, but do not draw holes
        for( GAL_LAYER_ID layer : { LAYER_PADS_TH, LAYER_VIA_THROUGH, LAYER_VIA_MICROVIA, LAYER_VIA_BBLIND } )
        {
            if( ( aLayerSet & LSET::AllCuMask() ).any() )   // Items visible on any copper layer
                aView.SetLayerVisible( layer, true );
            else
                renderSettings->SetLayerColor( layer, invisible_color );
        }

        // Keep certain items always enabled/disabled and just rely on the layer visibility
        // Note LAYER_PADS_SMD_FR, LAYER_PADS_SMD_BK, LAYER_PADS_TH are enabled here because paths must
        // be drawn on some other (technical) layers.
        const int alwaysEnabled[] =
                {
                    LAYER_FP_TEXT, LAYER_FP_VALUES, LAYER_FP_REFERENCES,
                    LAYER_FOOTPRINTS_FR, LAYER_FOOTPRINTS_BK,
                    LAYER_TRACKS, LAYER_VIAS,
                    LAYER_ZONES,
                    LAYER_PADS, LAYER_PADS_SMD_FR, LAYER_PADS_SMD_BK, LAYER_PADS_TH
                };

        for( int layer : alwaysEnabled )
            aView.SetLayerVisible( layer, true );
    }

    if( m_pcbnewSettings.m_DrillMarks != DRILL_MARKS::NO_DRILL_SHAPE )
    {
        // Enable hole layers to draw drill marks
        for( int layer : { LAYER_PAD_PLATEDHOLES, LAYER_NON_PLATEDHOLES, LAYER_VIA_HOLES } )
        {
            aView.SetLayerVisible( layer, true );
            aView.SetTopLayer( layer, true );
        }

        if( m_pcbnewSettings.m_DrillMarks == DRILL_MARKS::FULL_DRILL_SHAPE
                && !m_settings.m_blackWhite )
        {
            for( int layer : { LAYER_PAD_HOLEWALLS, LAYER_VIA_HOLEWALLS } )
            {
                aView.SetLayerVisible( layer, true );
                aView.SetTopLayer( layer, true );
            }
        }
    }
}


void PCBNEW_PRINTOUT::setupPainter( KIGFX::PAINTER& aPainter )
{
    BOARD_PRINTOUT::setupPainter( aPainter );

    KIGFX::PCB_PRINT_PAINTER& painter = dynamic_cast<KIGFX::PCB_PRINT_PAINTER&>( aPainter );

    switch( m_pcbnewSettings.m_DrillMarks )
    {
    case DRILL_MARKS::NO_DRILL_SHAPE:
        painter.SetDrillMarks( false, 0 );
        break;

    case DRILL_MARKS::SMALL_DRILL_SHAPE:
        painter.SetDrillMarks( false, pcbIUScale.mmToIU( ADVANCED_CFG::GetCfg().m_SmallDrillMarkSize ) );

        painter.GetSettings()->SetLayerColor( LAYER_PAD_PLATEDHOLES, COLOR4D::BLACK );
        painter.GetSettings()->SetLayerColor( LAYER_NON_PLATEDHOLES, COLOR4D::BLACK );
        painter.GetSettings()->SetLayerColor( LAYER_VIA_HOLES, COLOR4D::BLACK );
        break;

    case DRILL_MARKS::FULL_DRILL_SHAPE:
        painter.SetDrillMarks( true );

        painter.GetSettings()->SetLayerColor( LAYER_PAD_PLATEDHOLES, COLOR4D::BLACK );
        painter.GetSettings()->SetLayerColor( LAYER_NON_PLATEDHOLES, COLOR4D::BLACK );
        painter.GetSettings()->SetLayerColor( LAYER_VIA_HOLES, COLOR4D::BLACK );
        break;
    }
}


void PCBNEW_PRINTOUT::setupGal( KIGFX::GAL* aGal )
{
    BOARD_PRINTOUT::setupGal( aGal );
    aGal->SetWorldUnitLength( 0.001/pcbIUScale.IU_PER_MM /* 1 nm */ / 0.0254 /* 1 inch in meters */ );
}


BOX2I PCBNEW_PRINTOUT::getBoundingBox()
{
    bool showHiddenText = m_pcbnewSettings.m_AsItemCheckboxes
                            && m_board->IsElementVisible( LAYER_HIDDEN_TEXT );

    return m_board->ComputeBoundingBox( false, showHiddenText );
}


std::unique_ptr<KIGFX::PAINTER> PCBNEW_PRINTOUT::getPainter( KIGFX::GAL* aGal )
{
    return std::make_unique<KIGFX::PCB_PRINT_PAINTER>( aGal );
}


KIGFX::PCB_PRINT_PAINTER::PCB_PRINT_PAINTER( GAL* aGal ) :
        PCB_PAINTER( aGal, FRAME_PCB_EDITOR ),
        m_drillMarkReal( false ),
        m_drillMarkSize( 0 )
{ }


PAD_DRILL_SHAPE KIGFX::PCB_PRINT_PAINTER::getDrillShape( const PAD* aPad ) const
{
    return m_drillMarkReal ? KIGFX::PCB_PAINTER::getDrillShape( aPad ) : PAD_DRILL_SHAPE::CIRCLE;
}


SHAPE_SEGMENT KIGFX::PCB_PRINT_PAINTER::getPadHoleShape( const PAD* aPad ) const
{
    if( m_drillMarkReal )
        return KIGFX::PCB_PAINTER::getPadHoleShape( aPad );
    else
        return SHAPE_SEGMENT( aPad->GetPosition(), aPad->GetPosition(), m_drillMarkSize );
}


int KIGFX::PCB_PRINT_PAINTER::getViaDrillSize( const PCB_VIA* aVia ) const
{
    return m_drillMarkReal ? KIGFX::PCB_PAINTER::getViaDrillSize( aVia ) : m_drillMarkSize;
}