/*
 * This program source code file is part of KiCad, a free EDA CAD application.
 *
 * Copyright (C) 2013 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
 */

#ifndef ITEM_STATE_H_
#define ITEM_STATE_H_

#include <deque>
#include <class_board_item.h>

/**
 * Class ITEM_STATE
 *
 * Provides means for modifying properties of groups of items and gives possibility of rolling back
 * the introduced changes. Does not take ownership of modified items, neither takes care of
 * refreshing.
 */
class ITEM_STATE
{
public:
    ITEM_STATE() :
        m_movement( 0.0, 0.0 ), m_flips( 0 ), m_rotation( 0.0 )
    {
#ifdef __WXDEBUG__
        m_canSave = true;
#endif
    }

    /**
     * Function Save()
     *
     * Adds an item and saves it's state.
     * @param aItem is the item to be added.
     */
    void Save( BOARD_ITEM* aItem )
    {
#ifdef __WXDEBUG__
        wxASSERT_MSG( m_canSave, wxT( "You cannot save items after issuing commands. You have "
                                      "either RestoreAll() or Apply() before adding items!" ) );
#endif
        m_items.push_back( aItem );
    }

    /**
     * Function RestoreAll()
     *
     * Rollbacks all the changes to the initial state.
     */
    void RestoreAll()
    {
        // Check if there is a not saved movement command
        saveMovement();

        std::deque<BOARD_ITEM*>::iterator it, it_end;
        std::deque<COMMAND>::iterator cmd, cmd_end;

        for( it = m_items.begin(), it_end = m_items.end(); it != it_end; ++it )
        {
            for( cmd = m_commands.begin(), cmd_end = m_commands.end(); cmd != cmd_end; ++cmd )
                cmd->Revert( *it );
        }

        reset();
    }

    /**
     * Function Apply()
     *
     * Resets the state, clears the list of items & changes, so the object can be reused for
     * other items.
     */
    void Apply()
    {
        reset();
    }

    /**
     * Function Move()
     *
     * Moves stored items by a given vector.
     * @param aMovement is the movement vector.
     */
    void Move( const VECTOR2D& aMovement )
    {
#ifdef __WXDEBUG__
        m_canSave = false;
#endif
        std::deque<BOARD_ITEM*>::iterator it, it_end;

        for( it = m_items.begin(), it_end = m_items.end(); it != it_end; ++it )
            (*it)->Move( wxPoint( aMovement.x, aMovement.y ) );

        m_movement += aMovement;
    }

    /**
     * Function Rotate()
     *
     * Rotates stored items by a given angle.
     * @param aAngle is the angle (in decidegrees).
     */
    void Rotate( const VECTOR2D& aPoint, double aAngle )
    {
#ifdef __WXDEBUG__
        m_canSave = false;
#endif
        saveMovement();
        m_commands.push_front( COMMAND( COMMAND::ROTATE, aPoint, aAngle ) );

        std::deque<BOARD_ITEM*>::iterator it, it_end;

        for( it = m_items.begin(), it_end = m_items.end(); it != it_end; ++it )
            (*it)->Rotate( wxPoint( aPoint.x, aPoint.y ), aAngle );

        m_rotation += aAngle;
    }

    /**
     * Function Flip()
     *
     * Changes the board side for stored items.
     * @param aPoint is the rotation point.
     */
    void Flip( const VECTOR2D& aPoint )
    {
#ifdef __WXDEBUG__
        m_canSave = false;
#endif
        saveMovement();
        m_commands.push_front( COMMAND( COMMAND::FLIP, aPoint ) );

        std::deque<BOARD_ITEM*>::iterator it, it_end;

        for( it = m_items.begin(), it_end = m_items.end(); it != it_end; ++it )
            (*it)->Flip( wxPoint( aPoint.x, aPoint.y ) );

        m_flips++;
    }

    /**
     * Function ToggleVisibility()
     *
     * Switches the visibility property of stored items.
     */
    void ToggleVisibility()
    {
#ifdef __WXDEBUG__
        m_canSave = false;
#endif
        m_commands.push_front( COMMAND( COMMAND::VISIBILITY ) );

        std::deque<BOARD_ITEM*>::iterator it, it_end;

        for( it = m_items.begin(), it_end = m_items.end(); it != it_end; ++it )
            (*it)->ViewSetVisible( !(*it)->ViewIsVisible() );
    }

    /**
     * Function GetUpdateFlag()
     *
     * Returns information on what kind of update should be applied to items in order to display
     * them properly.
     * @return Flag required to refresh items.
     */
    KIGFX::VIEW_ITEM::VIEW_UPDATE_FLAGS GetUpdateFlag() const
    {
        if( m_flips % 2 == 1 ) // If number of flips is odd, then we need to change layers
            return KIGFX::VIEW_ITEM::LAYERS;
        else if( m_movement.x != 0.0 || m_movement.y != 0.0 || m_rotation != 0.0 )
            return KIGFX::VIEW_ITEM::GEOMETRY;

        return KIGFX::VIEW_ITEM::APPEARANCE;
    }

private:
    /// COMMAND stores modifications that were done to items
    struct COMMAND
    {
        /// Type of command
        enum TYPE { MOVE, ROTATE, FLIP, VISIBILITY };
        TYPE m_type;

        /// Point where flip/rotation occurred or movement vector
        VECTOR2D m_point;

        /// Used only for rotation
        double m_angle;

        COMMAND( TYPE aType, VECTOR2D aPoint = VECTOR2D( 0.0, 0.0 ), double aAngle = 0.0 ) :
            m_type( aType ), m_point( aPoint ), m_angle( aAngle ) {};

        void Revert( BOARD_ITEM* aItem )
        {
            switch( m_type )
            {
            case MOVE:
                aItem->Move( wxPoint( -m_point.x, -m_point.y ) );
                break;

            case ROTATE:
                aItem->Rotate( wxPoint( m_point.x, m_point.y ), -m_angle );
                break;

            case FLIP:
                aItem->Flip( wxPoint( m_point.x, m_point.y ) );
                break;

            case VISIBILITY:
                aItem->ViewSetVisible( !aItem->ViewIsVisible() );
                break;
            }
        }
    };

    /// Adds a MOVEMENT command basing on the current movement vector
    void saveMovement()
    {
        if( m_movement.x != 0.0 || m_movement.y != 0.0 )
        {
            m_commands.push_front( COMMAND( COMMAND::MOVE, m_movement ) );

            m_movement.x = 0.0;
            m_movement.y = 0.0;
        }
    }

    /// Restores the initial state
    void reset()
    {
        m_movement.x = 0.0;
        m_movement.y = 0.0;
        m_flips = 0;
        m_rotation = 0.0;

        m_items.clear();
        m_commands.clear();

#ifdef __WXDEBUG__
        m_canSave = true;
#endif
    }

    /// List of issued commands
    std::deque<BOARD_ITEM*> m_items;

    /// List of items that are affected by commands
    std::deque<COMMAND> m_commands;

    /// Current movement vector (updated by Move() command)
    VECTOR2D m_movement;

    /// Number of flips applied to items
    unsigned int m_flips;

    /// Total rotation applied to items
    double m_rotation;

#ifdef __WXDEBUG__
    /// Debug flag assuring that functions are called in proper order
    bool m_canSave;
#endif
};

#endif /* ITEM_STATE_H_ */