/*
 * This program source code file is part of KiCad, a free EDA CAD application.
 *
 * Copyright (C) 1992-2013 Jean-Pierre Charras <jp.charras at wanadoo.fr>.
 * Copyright (C) 1992-2023 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
 */


/*
 * the class DS_DATA_ITEM (and derived) defines
 * a basic shape of a drawing sheet (frame references and title block)
 * Basic shapes are line, rect and texts
 * the DS_DATA_ITEM coordinates units is the mm, and are relative to
 * one of 4 page corners.
 *
 * These items cannot be drawn or plot "as this". they should be converted
 * to a "draw list" (DS_DRAW_ITEM_BASE and derived items)

 * The list of these items is stored in a DS_DATA_MODEL instance.
 *
 * When building the draw list:
 * the DS_DATA_MODEL is used to create a DS_DRAW_ITEM_LIST
 *  coordinates are converted to draw/plot coordinates.
 *  texts are expanded if they contain format symbols.
 *  Items with m_RepeatCount > 1 are created m_RepeatCount times
 *
 * the DS_DATA_MODEL is created only once.
 * the DS_DRAW_ITEM_LIST is created each time the drawing sheet is plotted/drawn
 *
 * the DS_DATA_MODEL instance is created from a S expression which
 * describes the drawing sheet (can be the default drawing sheet or a custom file).
 */

#include <gr_text.h>
#include <math/util.h>      // for KiROUND
#include <view/view.h>
#include <title_block.h>
#include <drawing_sheet/ds_data_model.h>
#include <drawing_sheet/ds_data_item.h>
#include <drawing_sheet/ds_draw_item.h>
#include <drawing_sheet/ds_painter.h>
#include <trigo.h>

using KIGFX::COLOR4D;


DS_DATA_ITEM::DS_DATA_ITEM( DS_ITEM_TYPE aType )
{
    m_pageOption = ALL_PAGES;
    m_type = aType;
    m_RepeatCount = 1;
    m_IncrementLabel = 1;
    m_LineWidth = 0;
}


DS_DATA_ITEM::~DS_DATA_ITEM()
{
    for( DS_DRAW_ITEM_BASE* item : m_drawItems )
        delete item;
}


void DS_DATA_ITEM::SyncDrawItems( DS_DRAW_ITEM_LIST* aCollector, KIGFX::VIEW* aView )
{
    int pensize = GetPenSizeIU();

    if( pensize == 0 )
        pensize = aCollector ? aCollector->GetDefaultPenSize() : 0;

    std::map<size_t, EDA_ITEM_FLAGS> itemFlags;
    DS_DRAW_ITEM_BASE*               item = nullptr;

    for( size_t i = 0; i < m_drawItems.size(); ++i )
    {
        item = m_drawItems[ i ];
        itemFlags[ i ] = item->GetFlags();

        if( aCollector )
            aCollector->Remove( item );

        if( aView )
            aView->Remove( item );

        delete item;
    }

    m_drawItems.clear();

    for( int j = 0; j < m_RepeatCount; j++ )
    {
        if( j > 0 && !IsInsidePage( j ) )
            continue;

        if( m_type == DS_SEGMENT )
            item = new DS_DRAW_ITEM_LINE( this, j, GetStartPosIU( j ), GetEndPosIU( j ), pensize );
        else if( m_type == DS_RECT )
            item = new DS_DRAW_ITEM_RECT( this, j, GetStartPosIU( j ), GetEndPosIU( j ), pensize );
        else
        {
            wxFAIL_MSG( wxS( "Unknown drawing sheet item type" ) );
            continue;
        }

        item->SetFlags( itemFlags[ j ] );
        m_drawItems.push_back( item );

        if( aCollector )
            aCollector->Append( item );

        if( aView )
            aView->Add( item );
    }
}


int DS_DATA_ITEM::GetPenSizeIU()
{
    DS_DATA_MODEL& model = DS_DATA_MODEL::GetTheInstance();

    if( m_LineWidth != 0 )
        return KiROUND( m_LineWidth * model.m_WSunits2Iu );
    else
        return KiROUND( model.m_DefaultLineWidth * model.m_WSunits2Iu );
}


void DS_DATA_ITEM::MoveToIU( const VECTOR2I& aPosition )
{
    VECTOR2D pos_mm;
    pos_mm.x = aPosition.x / DS_DATA_MODEL::GetTheInstance().m_WSunits2Iu;
    pos_mm.y = aPosition.y / DS_DATA_MODEL::GetTheInstance().m_WSunits2Iu;

    MoveTo( pos_mm );
}


void DS_DATA_ITEM::MoveTo( const VECTOR2D& aPosition )
{
    VECTOR2D vector = aPosition - GetStartPos();
    VECTOR2D endpos = vector + GetEndPos();

    MoveStartPointTo( aPosition );
    MoveEndPointTo( endpos );

    for( DS_DRAW_ITEM_BASE* drawItem : m_drawItems )
    {
        drawItem->SetPosition( GetStartPosIU( drawItem->GetIndexInPeer() ) );
        drawItem->SetEnd( GetEndPosIU( drawItem->GetIndexInPeer() ) );
    }
}


void DS_DATA_ITEM::MoveStartPointTo( const VECTOR2D& aPosition )
{
    DS_DATA_MODEL& model = DS_DATA_MODEL::GetTheInstance();
    VECTOR2D       position;

    // Calculate the position of the starting point
    // relative to the reference corner
    // aPosition is the position relative to the right top paper corner
    switch( m_Pos.m_Anchor )
    {
    case RB_CORNER:
        position = model.m_RB_Corner - aPosition;
        break;

    case RT_CORNER:
        position.x = model.m_RB_Corner.x - aPosition.x;
        position.y = aPosition.y - model.m_LT_Corner.y;
        break;

    case LB_CORNER:
        position.x = aPosition.x - model.m_LT_Corner.x;
        position.y = model.m_RB_Corner.y - aPosition.y;
        break;

    case LT_CORNER:
        position = aPosition - model.m_LT_Corner;
        break;
    }

    m_Pos.m_Pos = position;
}


void DS_DATA_ITEM::MoveStartPointToIU( const VECTOR2I& aPosition )
{
    VECTOR2D pos_mm( aPosition.x / DS_DATA_MODEL::GetTheInstance().m_WSunits2Iu,
                     aPosition.y / DS_DATA_MODEL::GetTheInstance().m_WSunits2Iu );

    MoveStartPointTo( pos_mm );
}


void DS_DATA_ITEM::MoveEndPointTo( const VECTOR2D& aPosition )
{
    DS_DATA_MODEL& model = DS_DATA_MODEL::GetTheInstance();
    VECTOR2D       position;

    // Calculate the position of the starting point
    // relative to the reference corner
    // aPosition is the position relative to the right top paper corner
    switch( m_End.m_Anchor )
    {
    case RB_CORNER:
        position = model.m_RB_Corner - aPosition;
        break;

    case RT_CORNER:
        position.x = model.m_RB_Corner.x - aPosition.x;
        position.y = aPosition.y - model.m_LT_Corner.y;
        break;

    case LB_CORNER:
        position.x = aPosition.x - model.m_LT_Corner.x;
        position.y = model.m_RB_Corner.y - aPosition.y;
        break;

    case LT_CORNER:
        position = aPosition - model.m_LT_Corner;
        break;
    }

    // Modify m_End only for items having 2 coordinates
    switch( GetType() )
    {
    case DS_SEGMENT:
    case DS_RECT:
        m_End.m_Pos = position;
        break;

    default:
        break;
    }
}


void DS_DATA_ITEM::MoveEndPointToIU( const VECTOR2I& aPosition )
{
    VECTOR2D pos_mm;
    pos_mm.x = aPosition.x / DS_DATA_MODEL::GetTheInstance().m_WSunits2Iu;
    pos_mm.y = aPosition.y / DS_DATA_MODEL::GetTheInstance().m_WSunits2Iu;

    MoveEndPointTo( pos_mm );
}


const VECTOR2D DS_DATA_ITEM::GetStartPos( int ii ) const
{
    DS_DATA_MODEL& model = DS_DATA_MODEL::GetTheInstance();
    VECTOR2D       pos( m_Pos.m_Pos.x + ( m_IncrementVector.x * ii ),
                        m_Pos.m_Pos.y + ( m_IncrementVector.y * ii ) );

    switch( m_Pos.m_Anchor )
    {
        case RB_CORNER:      // right bottom corner
            pos = model.m_RB_Corner - pos;
            break;

        case RT_CORNER:      // right top corner
            pos.x = model.m_RB_Corner.x - pos.x;
            pos.y = model.m_LT_Corner.y + pos.y;
            break;

        case LB_CORNER:      // left bottom corner
            pos.x = model.m_LT_Corner.x + pos.x;
            pos.y = model.m_RB_Corner.y - pos.y;
            break;

        case LT_CORNER:      // left top corner
            pos = model.m_LT_Corner + pos;
            break;
    }

    return pos;
}


const VECTOR2I DS_DATA_ITEM::GetStartPosIU( int ii ) const
{
    VECTOR2D pos = GetStartPos( ii ) * DS_DATA_MODEL::GetTheInstance().m_WSunits2Iu;
    return VECTOR2I( KiROUND( pos.x ), KiROUND( pos.y ) );
}


const VECTOR2D DS_DATA_ITEM::GetEndPos( int ii ) const
{
    VECTOR2D pos( m_End.m_Pos.x + ( m_IncrementVector.x * ii ),
                  m_End.m_Pos.y + ( m_IncrementVector.y * ii ) );

    switch( m_End.m_Anchor )
    {
    case RB_CORNER:      // right bottom corner
        pos = DS_DATA_MODEL::GetTheInstance().m_RB_Corner - pos;
        break;

    case RT_CORNER:      // right top corner
        pos.x = DS_DATA_MODEL::GetTheInstance().m_RB_Corner.x - pos.x;
        pos.y = DS_DATA_MODEL::GetTheInstance().m_LT_Corner.y + pos.y;
        break;

    case LB_CORNER:      // left bottom corner
        pos.x = DS_DATA_MODEL::GetTheInstance().m_LT_Corner.x + pos.x;
        pos.y = DS_DATA_MODEL::GetTheInstance().m_RB_Corner.y - pos.y;
        break;

    case LT_CORNER:      // left top corner
        pos = DS_DATA_MODEL::GetTheInstance().m_LT_Corner + pos;
        break;
    }

    return pos;
}


const VECTOR2I DS_DATA_ITEM::GetEndPosIU( int ii ) const
{
    VECTOR2D pos = GetEndPos( ii );
    pos = pos * DS_DATA_MODEL::GetTheInstance().m_WSunits2Iu;
    return VECTOR2I( KiROUND( pos.x ), KiROUND( pos.y ) );
}


bool DS_DATA_ITEM::IsInsidePage( int ii ) const
{
    DS_DATA_MODEL& model = DS_DATA_MODEL::GetTheInstance();

    for( const VECTOR2D& pos : { GetStartPos( ii ), GetEndPos( ii ) } )
    {
        if( model.m_RB_Corner.x < pos.x || model.m_LT_Corner.x > pos.x )
            return false;

        if( model.m_RB_Corner.y < pos.y || model.m_LT_Corner.y > pos.y )
            return false;
    }

    return true;
}


const wxString DS_DATA_ITEM::GetClassName() const
{
    wxString name;

    switch( GetType() )
    {
        case DS_TEXT:        name = _( "Text" );           break;
        case DS_SEGMENT:     name = _( "Line" );           break;
        case DS_RECT:        name = _( "Rectangle" );      break;
        case DS_POLYPOLYGON: name = _( "Imported Shape" ); break;
        case DS_BITMAP:      name = _( "Image" );          break;
    }

    return name;
}


DS_DATA_ITEM_POLYGONS::DS_DATA_ITEM_POLYGONS() :
        DS_DATA_ITEM( DS_POLYPOLYGON )
{
}


void DS_DATA_ITEM_POLYGONS::SyncDrawItems( DS_DRAW_ITEM_LIST* aCollector, KIGFX::VIEW* aView )
{
    std::map<int, EDA_ITEM_FLAGS> itemFlags;
    DS_DRAW_ITEM_BASE*          item = nullptr;

    for( size_t i = 0; i < m_drawItems.size(); ++i )
    {
        item = m_drawItems[ i ];
        itemFlags[ i ] = item->GetFlags();

        if( aCollector )
            aCollector->Remove( item );

        if( aView )
            aView->Remove( item );

        delete item;
    }

    m_drawItems.clear();

    for( int j = 0; j < m_RepeatCount; j++ )
    {
        if( j > 0 && !IsInsidePage( j ) )
            continue;

        int pensize = GetPenSizeIU();
        auto poly_shape = new DS_DRAW_ITEM_POLYPOLYGONS( this, j, GetStartPosIU( j ), pensize );
        poly_shape->SetFlags( itemFlags[ j ] );
        m_drawItems.push_back( poly_shape );

        // Transfer all outlines (basic polygons)
        SHAPE_POLY_SET& polygons = poly_shape->GetPolygons();

        for( int kk = 0; kk < GetPolyCount(); kk++ )
        {
            // Create new outline
            unsigned ist = GetPolyIndexStart( kk );
            unsigned iend = GetPolyIndexEnd( kk );

            polygons.NewOutline();

            while( ist <= iend )
                polygons.Append( GetCornerPositionIU( ist++, j ) );
        }

        if( aCollector )
            aCollector->Append( poly_shape );

        if( aView )
            aView->Add( poly_shape );
    }
}


int DS_DATA_ITEM_POLYGONS::GetPenSizeIU()
{
    return KiROUND( m_LineWidth * DS_DATA_MODEL::GetTheInstance().m_WSunits2Iu );
}


const VECTOR2D DS_DATA_ITEM_POLYGONS::GetCornerPosition( unsigned aIdx, int aRepeat ) const
{
    VECTOR2D pos = m_Corners[aIdx];

    // Rotation:
    RotatePoint( &pos.x, &pos.y, m_Orient );
    pos += GetStartPos( aRepeat );
    return pos;
}


void DS_DATA_ITEM_POLYGONS::SetBoundingBox()
{
    if( m_Corners.size() == 0 )
    {
        m_minCoord.x = m_maxCoord.x = 0.0;
        m_minCoord.y = m_maxCoord.y = 0.0;
        return;
    }

    VECTOR2I pos;
    pos = m_Corners[0];
    RotatePoint( &pos.x, &pos.y, m_Orient );
    m_minCoord = m_maxCoord = pos;

    for( unsigned ii = 1; ii < m_Corners.size(); ii++ )
    {
        pos = m_Corners[ii];
        RotatePoint( &pos.x, &pos.y, m_Orient );

        if( m_minCoord.x > pos.x )
            m_minCoord.x = pos.x;

        if( m_minCoord.y > pos.y )
            m_minCoord.y = pos.y;

        if( m_maxCoord.x < pos.x )
            m_maxCoord.x = pos.x;

        if( m_maxCoord.y < pos.y )
            m_maxCoord.y = pos.y;
    }
}


bool DS_DATA_ITEM_POLYGONS::IsInsidePage( int ii ) const
{
    DS_DATA_MODEL& model = DS_DATA_MODEL::GetTheInstance();

    VECTOR2D pos = GetStartPos( ii );
    pos += m_minCoord;  // left top pos of bounding box

    if( model.m_LT_Corner.x > pos.x || model.m_LT_Corner.y > pos.y )
        return false;

    pos = GetStartPos( ii );
    pos += m_maxCoord;  // right bottom pos of bounding box

    if( model.m_RB_Corner.x < pos.x || model.m_RB_Corner.y < pos.y )
        return false;

    return true;
}


const VECTOR2I DS_DATA_ITEM_POLYGONS::GetCornerPositionIU( unsigned aIdx, int aRepeat ) const
{
    VECTOR2D pos = GetCornerPosition( aIdx, aRepeat );
    pos = pos * DS_DATA_MODEL::GetTheInstance().m_WSunits2Iu;
    return VECTOR2I( int( pos.x ), int( pos.y ) );
}


DS_DATA_ITEM_TEXT::DS_DATA_ITEM_TEXT( const wxString& aTextBase ) :
        DS_DATA_ITEM( DS_TEXT )
{
    m_TextBase = aTextBase;
    m_IncrementLabel = 1;
    m_Hjustify = GR_TEXT_H_ALIGN_LEFT;
    m_Vjustify = GR_TEXT_V_ALIGN_CENTER;
    m_Italic = false;
    m_Bold = false;
    m_Font = nullptr;
    m_TextColor = COLOR4D::UNSPECIFIED;
    m_Orient = 0.0;
    m_LineWidth = 0.0;      // 0 means use default value
}


void DS_DATA_ITEM_TEXT::SyncDrawItems( DS_DRAW_ITEM_LIST* aCollector, KIGFX::VIEW* aView )
{
    int   pensize = GetPenSizeIU();
    bool  multilines = false;

    if( DS_DATA_MODEL::GetTheInstance().m_EditMode )
    {
        m_FullText = m_TextBase;
    }
    else
    {
        m_FullText = aCollector ? aCollector->BuildFullText( m_TextBase ) : wxString();
        multilines = ReplaceAntiSlashSequence();
    }

    if( pensize == 0 )
        pensize = aCollector ? aCollector->GetDefaultPenSize() : 1;

    SetConstrainedTextSize();
    VECTOR2I textsize;

    textsize.x = KiROUND( m_ConstrainedTextSize.x * DS_DATA_MODEL::GetTheInstance().m_WSunits2Iu );
    textsize.y = KiROUND( m_ConstrainedTextSize.y * DS_DATA_MODEL::GetTheInstance().m_WSunits2Iu );

    if( m_Bold )
        pensize = GetPenSizeForBold( std::min( textsize.x, textsize.y ) );

    std::map<size_t, EDA_ITEM_FLAGS> itemFlags;
    DS_DRAW_ITEM_TEXT*               text = nullptr;

    for( size_t i = 0; i < m_drawItems.size(); ++i )
    {
        text = (DS_DRAW_ITEM_TEXT*) m_drawItems[ i ];
        itemFlags[ i ] = text->GetFlags();

        if( aCollector )
            aCollector->Remove( text );

        if( aView )
            aView->Remove( text );

        delete text;
    }

    m_drawItems.clear();

    for( int j = 0; j < m_RepeatCount; ++j )
    {
        if( j > 0 && !IsInsidePage( j ) )
            continue;

        EDA_IU_SCALE iuscale = aCollector ? aCollector->GetIuScale()
                                          : DS_DATA_MODEL::GetTheInstance().m_WSunits2Iu;

        text = new DS_DRAW_ITEM_TEXT( iuscale, this, j, m_FullText, GetStartPosIU( j ), textsize,
                                      pensize, m_Font, m_Italic, m_Bold, m_TextColor );

        text->SetFlags( itemFlags[ j ] );
        m_drawItems.push_back( text );

        if( aCollector )
            aCollector->Append( text );

        if( aView )
            aView->Add( text );

        text->SetHorizJustify( m_Hjustify );
        text->SetVertJustify( m_Vjustify );
        text->SetTextAngle( EDA_ANGLE( m_Orient, DEGREES_T ) );
        text->SetMultilineAllowed( multilines );

        // Increment label for the next text (has no meaning for multiline texts)
        if( m_RepeatCount > 1 && !multilines )
            IncrementLabel( ( j + 1 ) * m_IncrementLabel );
    }
}


int DS_DATA_ITEM_TEXT::GetPenSizeIU()
{
    DS_DATA_MODEL& model = DS_DATA_MODEL::GetTheInstance();

    if( m_LineWidth != 0 )
        return KiROUND( m_LineWidth * model.m_WSunits2Iu );
    else
        return KiROUND( model.m_DefaultTextThickness * model.m_WSunits2Iu );
}


void DS_DATA_ITEM_TEXT::IncrementLabel( int aIncr )
{
    int last = m_TextBase.Len() -1;

    wxChar lbchar = m_TextBase[last];
    m_FullText = m_TextBase;
    m_FullText.RemoveLast();

    if( lbchar >= '0' &&  lbchar <= '9' )
        // A number is expected:
        m_FullText << (int)( aIncr + lbchar - '0' );
    else
        m_FullText << (wxChar) ( aIncr + lbchar );
}


// Replace the '\''n' sequence by EOL
// and the sequence  '\''\' by only one '\' in m_FullText
// if m_FullText is a multiline text (i.e.contains '\n') return true
bool DS_DATA_ITEM_TEXT::ReplaceAntiSlashSequence()
{
    bool multiline = false;

    for( unsigned ii = 0; ii < m_FullText.Len(); ii++ )
    {
        if( m_FullText[ii] == '\n' )
            multiline = true;

        else if( m_FullText[ii] == '\\' )
        {
            if( ++ii >= m_FullText.Len() )
                break;

            if( m_FullText[ii] == '\\' )
            {
                // a double \\ sequence is replaced by a single \ char
                m_FullText.Remove(ii, 1);
                ii--;
            }
            else if( m_FullText[ii] == 'n' )
            {
                // Replace the "\n" sequence by a EOL char
                multiline = true;
                m_FullText[ii] = '\n';
                m_FullText.Remove(ii-1, 1);
                ii--;
            }
        }
    }

    return multiline;
}


void DS_DATA_ITEM_TEXT::SetConstrainedTextSize()
{
    m_ConstrainedTextSize = m_TextSize;

    if( m_ConstrainedTextSize.x == 0  )
        m_ConstrainedTextSize.x = DS_DATA_MODEL::GetTheInstance().m_DefaultTextSize.x;

    if( m_ConstrainedTextSize.y == 0 )
        m_ConstrainedTextSize.y = DS_DATA_MODEL::GetTheInstance().m_DefaultTextSize.y;
}


void DS_DATA_ITEM_BITMAP::SyncDrawItems( DS_DRAW_ITEM_LIST* aCollector, KIGFX::VIEW* aView )
{
    std::map<size_t, EDA_ITEM_FLAGS> itemFlags;
    DS_DRAW_ITEM_BASE*          item = nullptr;

    for( size_t i = 0; i < m_drawItems.size(); ++i )
    {
        item = m_drawItems[ i ];
        itemFlags[ i ] = item->GetFlags();

        if( aCollector )
            aCollector->Remove( item );

        if( aView )
            aView->Remove( item );

        delete item;
    }

    if( aCollector )
    {
        double pix_size_iu = aCollector->GetMilsToIUfactor() * 1000 / m_ImageBitmap->GetPPI();
        m_ImageBitmap->SetPixelSizeIu( pix_size_iu );
    }

    if( !m_ImageBitmap->GetOriginalImageData() )
        return;

    m_drawItems.clear();

    for( int j = 0; j < m_RepeatCount; j++ )
    {
        if( j > 0 && !IsInsidePage( j ) )
            continue;

        DS_DRAW_ITEM_BITMAP* bitmap = new DS_DRAW_ITEM_BITMAP( this, j, GetStartPosIU( j ) );

        bitmap->SetFlags( itemFlags[ j ] );
        m_drawItems.push_back( bitmap );

        if( aCollector )
            aCollector->Append( bitmap );

        if( aView )
            aView->Add( bitmap );
    }
}


int DS_DATA_ITEM_BITMAP::GetPPI() const
{
    if( m_ImageBitmap )
        return m_ImageBitmap->GetPPI() / m_ImageBitmap->GetScale();

    return 300;
}


void DS_DATA_ITEM_BITMAP::SetPPI( int aBitmapPPI )
{
    if( m_ImageBitmap )
        m_ImageBitmap->SetScale( (double) m_ImageBitmap->GetPPI() / aBitmapPPI );
}
