/*
 * This program source code file is part of KiCad, a free EDA CAD application.
 *
 * Copyright (C) 2009 Jean-Pierre Charras, jaen-pierre.charras@gipsa-lab.inpg.com
 * Copyright (C) 2009 Wayne Stambaugh <stambaughw@verizon.net>
 * Copyright (C) 1992-2011 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
 */

/**
 * @file base_screen.cpp
 * @brief BASE_SCREEN object implementation.
 */

#include "fctsys.h"
#include "common.h"
#include "base_struct.h"
#include "class_base_screen.h"
#include "id.h"


#define CURSOR_SIZE 12  /* size of the cross cursor. */


BASE_SCREEN::BASE_SCREEN( KICAD_T aType ) : EDA_ITEM( aType )
{
    m_drawList         = NULL;   /* Draw items list */
    m_UndoRedoCountMax = 10;     /* undo/Redo command Max depth, 10 is a reasonable value */
    m_FirstRedraw      = TRUE;
    m_ScreenNumber     = 1;
    m_NumberOfScreen   = 1;  /* Hierarchy: Root: ScreenNumber = 1 */
    m_Zoom             = 32.0;
    m_Grid.m_Size      = wxRealPoint( 50, 50 );   /* Default grid size */
    m_Grid.m_Id        = ID_POPUP_GRID_LEVEL_50;
    m_Center           = true;
    m_CurrentSheetDesc = &g_Sheet_A4;
    m_IsPrinting       = false;
    m_ScrollPixelsPerUnitX = 1;
    m_ScrollPixelsPerUnitY = 1;

    InitDatas();
}


BASE_SCREEN::~BASE_SCREEN()
{
}


void BASE_SCREEN::InitDatas()
{
    if( m_Center )
    {
        m_crossHairPosition.x = m_crossHairPosition.y = 0;
        m_DrawOrg.x = -ReturnPageSize().x / 2;
        m_DrawOrg.y = -ReturnPageSize().y / 2;
    }
    else
    {
        m_DrawOrg.x = m_DrawOrg.y = 0;
        m_crossHairPosition.x = ReturnPageSize().x / 2;
        m_crossHairPosition.y = ReturnPageSize().y / 2;
    }

    m_O_Curseur.x = m_O_Curseur.y = 0;

    SetCurItem( NULL );

    m_FlagModified = false;   // Set when any change is made on broad.
    m_FlagSave = false;       // Used in auto save set when an auto save is required.
}


/**
 * Get screen units scalar.
 *
 * Default implementation returns scalar used for schematic screen.  The
 * internal units used by the schematic screen is 1 mil (0.001").  Override
 * this in derived classes that require internal units other than 1 mil.
 */
int BASE_SCREEN::GetInternalUnits( void )
{
    return EESCHEMA_INTERNAL_UNIT;
}


wxSize BASE_SCREEN::ReturnPageSize( void )
{
    int internal_units = GetInternalUnits();
    wxSize size = m_CurrentSheetDesc->m_Size;
    size.x =  (int)( (double)size.x * internal_units / 1000 );
    size.y =  (int)( (double)size.y * internal_units / 1000 );

    return size;
}

void BASE_SCREEN::SetPageSize( wxSize& aPageSize )
{
    int internal_units = GetInternalUnits();

    m_CurrentSheetDesc->m_Size.x = (int) ((double)aPageSize.x * 1000 / internal_units);
    m_CurrentSheetDesc->m_Size.y = (int) ((double)aPageSize.y * 1000 / internal_units);
}


/**
 * Function GetScalingFactor
 * @return the the current scale used to draw items on screen
 * draw coordinates are user coordinates * GetScalingFactor( )
 */
double BASE_SCREEN::GetScalingFactor() const
{
    double scale = 1.0 / GetZoom();
    return scale;
}

/**
 * Function SetScalingFactor
 * calculates the .m_Zoom member to have a given scaling factor
 * @param aScale - the the current scale used to draw items on screen
 * draw coordinates are user coordinates * GetScalingFactor()
 */
void BASE_SCREEN::SetScalingFactor(double aScale )
{
    double zoom = aScale;

    // Limit zoom to max and min allowed values:
    if (zoom < m_ZoomList[0])
        zoom = m_ZoomList[0];

    int idxmax = m_ZoomList.GetCount() - 1;

    if (zoom > m_ZoomList[idxmax])
        zoom = m_ZoomList[idxmax];

    SetZoom( zoom );
}

void BASE_SCREEN::SetZoomList( const wxArrayDouble& zoomlist )
{
    if( !m_ZoomList.IsEmpty() )
        m_ZoomList.Empty();

    m_ZoomList = zoomlist;
}


bool BASE_SCREEN::SetFirstZoom()
{
    if( m_ZoomList.IsEmpty() )
    {
        if( m_Zoom != 1.0 )
        {
            m_Zoom = 1.0;
            return true;
        }
    }
    else if( m_Zoom != m_ZoomList[0] )
    {
        m_Zoom = m_ZoomList[0];
        return true;
    }

    return false;
}


double BASE_SCREEN::GetZoom() const
{
    return m_Zoom;
}


bool BASE_SCREEN::SetZoom( double coeff )
{
    if( coeff == m_Zoom )
        return false;

    m_Zoom = coeff;

    return true;
}


bool BASE_SCREEN::SetNextZoom()
{
    size_t i;

    if( m_ZoomList.IsEmpty() || m_Zoom >= m_ZoomList.Last() )
        return false;

    for( i = 0; i < m_ZoomList.GetCount(); i++ )
    {
        if( m_Zoom < m_ZoomList[i] )
        {
            m_Zoom = m_ZoomList[i];
            return true;
        }
    }

    return false;
}


bool BASE_SCREEN::SetPreviousZoom()
{
    size_t i;

    if( m_ZoomList.IsEmpty() || m_Zoom <= m_ZoomList[0] )
        return false;

    for( i = m_ZoomList.GetCount(); i != 0; i-- )
    {
        if( m_Zoom > m_ZoomList[i - 1] )
        {
            m_Zoom = m_ZoomList[i - 1];
            return true;
        }
    }

    return false;
}


bool BASE_SCREEN::SetLastZoom()
{
    if( m_ZoomList.IsEmpty() || m_Zoom == m_ZoomList.Last() )
        return false;

    m_Zoom = m_ZoomList.Last();
    return true;
}


void BASE_SCREEN::SetGridList( GRIDS& gridlist )
{
    if( !m_grids.empty() )
        m_grids.clear();

    m_grids = gridlist;
}


void BASE_SCREEN::GetGrids( GRIDS& aList )
{
    for( size_t i = 0;  i < m_grids.size();  i++ )
        aList.push_back( m_grids[ i ] );
}


void BASE_SCREEN::SetGrid( const wxRealPoint& size )
{
    wxASSERT( !m_grids.empty() );

    size_t i;

    GRID_TYPE nearest_grid = m_grids[0];

    for( i = 0; i < m_grids.size(); i++ )
    {
        if( m_grids[i].m_Size == size )
        {
            m_Grid = m_grids[i];
            return;
        }

        // keep trace of the nearest grill size, if the exact size is not found
        if ( size.x < m_grids[i].m_Size.x )
            nearest_grid = m_grids[i];
    }

    m_Grid = nearest_grid;

    wxLogWarning( wxT( "Grid size( %f, %f ) not in grid list, falling back " ) \
                  wxT( "to grid size( %f, %f )." ),
                  size.x, size.y, m_Grid.m_Size.x, m_Grid.m_Size.y );
}


/* Set grid size from command ID. */
void BASE_SCREEN::SetGrid( int id  )
{
    wxASSERT( !m_grids.empty() );

    size_t i;

    for( i = 0; i < m_grids.size(); i++ )
    {
        if( m_grids[i].m_Id == id )
        {
            m_Grid = m_grids[i];
            return;
        }
    }

    m_Grid = m_grids[0];

    wxLogWarning( wxT( "Grid ID %d not in grid list, falling back to " ) \
                  wxT( "grid size( %g, %g )." ), id, m_Grid.m_Size.x,
                  m_Grid.m_Size.y );
}


void BASE_SCREEN::AddGrid( const GRID_TYPE& grid )
{
    size_t i;

    for( i = 0; i < m_grids.size(); i++ )
    {
        if( m_grids[i].m_Size == grid.m_Size && grid.m_Id != ID_POPUP_GRID_USER )
        {
            wxLogDebug( wxT( "Discarding duplicate grid size( %g, %g )." ),
                        grid.m_Size.x, grid.m_Size.y );
            return;
        }

        if( m_grids[i].m_Id == grid.m_Id )
        {
            wxLogDebug( wxT( "Changing grid ID %d from size( %g, %g ) to " ) \
                        wxT( "size( %g, %g )." ),
                        grid.m_Id, m_grids[i].m_Size.x,
                        m_grids[i].m_Size.y, grid.m_Size.x, grid.m_Size.y );
            m_grids[i].m_Size = grid.m_Size;
            return;
        }
    }

    m_grids.push_back( grid );
}


void BASE_SCREEN::AddGrid( const wxRealPoint& size, int id )
{
    GRID_TYPE grid;

    grid.m_Size = size;
    grid.m_Id = id;
    AddGrid( grid );
}


void BASE_SCREEN::AddGrid( const wxRealPoint& size, EDA_UNITS_T aUnit, int id )
{
    double x, y;
    wxRealPoint new_size;
    GRID_TYPE new_grid;

    switch( aUnit )
    {
    case MILLIMETRES:
        x = size.x / 25.4;
        y = size.y / 25.4;
        break;

    default:
    case INCHES:
    case UNSCALED_UNITS:
        x = size.x;
        y = size.y;
        break;
    }

    new_size.x = x * GetInternalUnits();
    new_size.y = y * GetInternalUnits();

    new_grid.m_Id = id;
    new_grid.m_Size = new_size;
    AddGrid( new_grid );
}


GRID_TYPE& BASE_SCREEN::GetGrid( size_t aIndex )
{
    wxCHECK_MSG( !m_grids.empty() && aIndex < m_grids.size(), m_Grid,
                 wxT( "Cannot get grid object outside the bounds of the grid list." ) );

    return m_grids[ aIndex ];
}


GRID_TYPE BASE_SCREEN::GetGrid()
{
    return m_Grid;
}


const wxPoint& BASE_SCREEN::GetGridOrigin()
{
    return m_GridOrigin;
}


wxRealPoint BASE_SCREEN::GetGridSize()
{
    return m_Grid.m_Size;
}


int BASE_SCREEN::GetGridId()
{
    return m_Grid.m_Id;
}


wxPoint BASE_SCREEN::GetNearestGridPosition( const wxPoint& aPosition, wxRealPoint* aGridSize )
{
    wxPoint pt;
    wxRealPoint gridSize;

    if( aGridSize )
        gridSize = *aGridSize;
    else
        gridSize = GetGridSize();

    wxPoint gridOrigin = m_GridOrigin;

    double offset = fmod( gridOrigin.x, gridSize.x );
    int x = wxRound( (aPosition.x - offset) / gridSize.x );
    pt.x = wxRound( x * gridSize.x + offset );

    offset = fmod( gridOrigin.y, gridSize.y );
    int y = wxRound( (aPosition.y - offset) / gridSize.y );
    pt.y = wxRound ( y * gridSize.y + offset );

    return pt;
}


wxPoint BASE_SCREEN::GetCursorPosition( bool aOnGrid, wxRealPoint* aGridSize )
{
    if( aOnGrid )
        return GetNearestGridPosition( m_crossHairPosition, aGridSize );

    return m_crossHairPosition;
}


wxPoint BASE_SCREEN::GetCrossHairScreenPosition() const
{
    wxPoint pos = m_crossHairPosition - m_DrawOrg;
    double scalar = GetScalingFactor();

    pos.x = wxRound( (double) pos.x * scalar );
    pos.y = wxRound( (double) pos.y * scalar );

    return pos;
}

void BASE_SCREEN::SetCrossHairPosition( const wxPoint& aPosition, bool aSnapToGrid )
{
    if( aSnapToGrid )
        m_crossHairPosition = GetNearestGridPosition( aPosition );
    else
        m_crossHairPosition = aPosition;
}


void BASE_SCREEN::ClearUndoRedoList()
{
    ClearUndoORRedoList( m_UndoList );
    ClearUndoORRedoList( m_RedoList );
}


void BASE_SCREEN::PushCommandToUndoList( PICKED_ITEMS_LIST* aNewitem )
{
    m_UndoList.PushCommand( aNewitem );

    /* Delete the extra items, if count max reached */
    int extraitems = GetUndoCommandCount() - m_UndoRedoCountMax;

    if( extraitems > 0 ) // Delete the extra items
        ClearUndoORRedoList( m_UndoList, extraitems );
}


void BASE_SCREEN::PushCommandToRedoList( PICKED_ITEMS_LIST* aNewitem )
{
    m_RedoList.PushCommand( aNewitem );

    /* Delete the extra items, if count max reached */
    int extraitems = GetRedoCommandCount() - m_UndoRedoCountMax;

    if( extraitems > 0 ) // Delete the extra items
        ClearUndoORRedoList( m_RedoList, extraitems );
}


PICKED_ITEMS_LIST* BASE_SCREEN::PopCommandFromUndoList( )
{
    return m_UndoList.PopCommand( );
}


PICKED_ITEMS_LIST* BASE_SCREEN::PopCommandFromRedoList( )
{
    return m_RedoList.PopCommand( );
}


void BASE_SCREEN::AddItem( EDA_ITEM* aItem )
{
    wxCHECK_RET( aItem != NULL, wxT( "Attempt to add NULL item pointer to " ) + GetClass() +
                 wxT( "item list" ) );
    m_items.push_back( aItem );
}


void BASE_SCREEN::InsertItem( EDA_ITEMS::iterator aIter, EDA_ITEM* aItem )
{
    wxCHECK_RET( aItem != NULL, wxT( "Attempt to insert NULL item pointer to " ) + GetClass() +
                 wxT( "item list" ) );
    m_items.insert( aIter, aItem );
}


#if defined(DEBUG)

void BASE_SCREEN::Show( int nestLevel, std::ostream& os )
{
    EDA_ITEM* item = m_drawList;

    // for now, make it look like XML, expand on this later.
    NestedSpace( nestLevel, os ) << '<' << GetClass().Lower().mb_str() << ">\n";

    for(  ; item;  item = item->Next() )
    {
        item->Show( nestLevel+1, os );
    }

    NestedSpace( nestLevel, os ) << "</" << GetClass().Lower().mb_str() << ">\n";
}

#endif
