/****************************************************/
/*	block_module_editor.cpp							*/
/* Handle block commands for the footprint editor	*/
/****************************************************/

#include "fctsys.h"
#include "gr_basic.h"

#include "common.h"
#include "pcbnew.h"
#include "autorout.h"
#include "pcbplot.h"
#include "trigo.h"

#include "protos.h"


#define BLOCK_COLOR BROWN
#define IS_SELECTED 1

/* Variables Locales */

/* Fonctions exportees */

/* Fonctions Locales */
static void DrawMovingBlockOutlines(WinEDA_DrawPanel * panel, wxDC * DC, bool erase);
static int MarkItemsInBloc(MODULE * module,
	EDA_Rect & Rect);

static void ClearMarkItems(MODULE * module);
static void CopyMarkedItems(MODULE *module, wxPoint offset);
static void MoveMarkedItems(MODULE *module, wxPoint offset);
static void MirrorMarkedItems(MODULE *module, wxPoint offset);
static void RotateMarkedItems(MODULE *module, wxPoint offset);
static void DeleteMarkedItems(MODULE *module);


/*************************************************************************/
int WinEDA_ModuleEditFrame::ReturnBlockCommand(int key)
/*************************************************************************/
/* Return the block command (BLOCK_MOVE, BLOCK_COPY...) corresponding to
	the key (ALT, SHIFT ALT ..) pressed when dragging mouse and left or middle button
	pressed
*/
{
int cmd;

	switch ( key )
		{
		default:
			cmd = key & 0x255;
		break;

		case -1:
			cmd = BLOCK_PRESELECT_MOVE;
			break;

		case 0:
			cmd = BLOCK_MOVE;
			break;

		case GR_KB_ALT:
			cmd = BLOCK_MIRROR_Y;
			break;

		case GR_KB_SHIFTCTRL:
			cmd = BLOCK_DELETE;
			break;

		case GR_KB_SHIFT:
			cmd = BLOCK_COPY;
			break;

		case GR_KB_CTRL:
			cmd = BLOCK_ROTATE;
			break;

		case MOUSE_MIDDLE:
			cmd = BLOCK_ZOOM;
			break;
		}

	return cmd;
}


/****************************************************/
int WinEDA_ModuleEditFrame::HandleBlockEnd(wxDC * DC)
/****************************************************/
/* Command BLOCK END (end of block sizing)
	return :
	0 if command finished (zoom, delete ...) 
	1 if HandleBlockPlace must follow (items found, and a block place command must follow)
*/
{
int ItemsCount = 0, MustDoPlace = 0;
MODULE * Currentmodule = m_Pcb->m_Modules;

	if ( GetScreen()->BlockLocate.m_BlockDrawStruct )
	{
		BlockState state = GetScreen()->BlockLocate.m_State;
		CmdBlockType command = GetScreen()->BlockLocate.m_Command;
		DrawPanel->ForceCloseManageCurseur(DrawPanel, DC);
		GetScreen()->BlockLocate.m_State =  state;
		GetScreen()->BlockLocate.m_Command = command;
		DrawPanel->ManageCurseur = DrawAndSizingBlockOutlines;
		DrawPanel->ForceCloseManageCurseur = AbortBlockCurrentCommand;
		GetScreen()->m_Curseur.x = GetScreen()->BlockLocate.GetRight();
		GetScreen()->m_Curseur.y = GetScreen()->BlockLocate.GetBottom();
		DrawPanel->MouseToCursorSchema();
	}

	switch( GetScreen()->BlockLocate.m_Command )
		{
		case  BLOCK_IDLE:
			DisplayError(this, wxT("Error in HandleBlockPLace") );
			break;

		case BLOCK_DRAG: /* Drag */
		case BLOCK_MOVE: /* Move */
		case BLOCK_COPY: /* Copy */
			ItemsCount = MarkItemsInBloc(Currentmodule, GetScreen()->BlockLocate);
			if ( ItemsCount )
			{
				MustDoPlace = 1;
				if(DrawPanel->ManageCurseur != NULL)
				{
					DrawPanel->ManageCurseur(DrawPanel, DC, FALSE);
					DrawPanel->ManageCurseur = DrawMovingBlockOutlines;
					DrawPanel->ManageCurseur(DrawPanel, DC, FALSE);
				}
				GetScreen()->BlockLocate.m_State = STATE_BLOCK_MOVE;
			DrawPanel->Refresh(TRUE);
			}
			break;

		case BLOCK_PRESELECT_MOVE: /* Move with preselection list*/
			MustDoPlace = 1;
			DrawPanel->ManageCurseur = DrawMovingBlockOutlines;
			GetScreen()->BlockLocate.m_State = STATE_BLOCK_MOVE;
			break;

		case BLOCK_DELETE: /* Delete */
			ItemsCount = MarkItemsInBloc(Currentmodule, GetScreen()->BlockLocate);
			if ( ItemsCount ) SaveCopyInUndoList(Currentmodule);
			DeleteMarkedItems(Currentmodule);
			break;
			
		case BLOCK_SAVE: /* Save */
		case BLOCK_PASTE:
			break;

		case BLOCK_ROTATE:
			ItemsCount = MarkItemsInBloc(Currentmodule, GetScreen()->BlockLocate);
			if ( ItemsCount ) SaveCopyInUndoList(Currentmodule);
			RotateMarkedItems(Currentmodule, GetScreen()->BlockLocate.Centre());
			break;


		case BLOCK_MIRROR_X:
		case BLOCK_MIRROR_Y:
		case BLOCK_INVERT: /* mirror */
			ItemsCount = MarkItemsInBloc(Currentmodule, GetScreen()->BlockLocate);
			if ( ItemsCount ) SaveCopyInUndoList(Currentmodule);
			MirrorMarkedItems(Currentmodule, GetScreen()->BlockLocate.Centre());
			break;

		case BLOCK_ZOOM: /* Window Zoom */
			Window_Zoom( GetScreen()->BlockLocate );
			break;

		case BLOCK_ABORT:
			break;
		
		case BLOCK_SELECT_ITEMS_ONLY:
			break;
		}

	if ( MustDoPlace <= 0 )
	{
		if ( GetScreen()->BlockLocate.m_Command  != BLOCK_SELECT_ITEMS_ONLY )
		{
			ClearMarkItems(Currentmodule);
		}
		GetScreen()->BlockLocate.m_Flags = 0;
		GetScreen()->BlockLocate.m_State = STATE_NO_BLOCK;
		GetScreen()->BlockLocate.m_Command = BLOCK_IDLE;
		DrawPanel->ManageCurseur = NULL;
		DrawPanel->ForceCloseManageCurseur = NULL;
		GetScreen()->m_CurrentItem = NULL;
		SetToolID(m_ID_current_state, DrawPanel->m_PanelDefaultCursor, wxEmptyString );
		DrawPanel->Refresh(TRUE);
	}


	return(MustDoPlace);
}


/******************************************************/
void WinEDA_ModuleEditFrame::HandleBlockPlace(wxDC * DC)
/******************************************************/
/* Routine to handle the BLOCK PLACE commande
	Last routine for block operation for:
	- block move & drag
	- block copie & paste
*/
{
bool err = FALSE;
MODULE * Currentmodule = m_Pcb->m_Modules;

	if(DrawPanel->ManageCurseur == NULL)
	{
		err = TRUE;
		DisplayError(this, wxT("HandleBlockPLace : ManageCurseur = NULL") );
	}

	GetScreen()->BlockLocate.m_State = STATE_BLOCK_STOP;

	switch(GetScreen()->BlockLocate.m_Command )
	{
		case  BLOCK_IDLE:
			err = TRUE;
			break;
		
		case BLOCK_DRAG: /* Drag */
		case BLOCK_MOVE: /* Move */
		case BLOCK_PRESELECT_MOVE: /* Move with preselection list*/
			GetScreen()->BlockLocate.m_BlockDrawStruct = NULL;
			SaveCopyInUndoList(Currentmodule);
			MoveMarkedItems(Currentmodule, GetScreen()->BlockLocate.m_MoveVector);
			DrawPanel->Refresh(TRUE);
			break;

		case BLOCK_COPY: /* Copy */
			GetScreen()->BlockLocate.m_BlockDrawStruct = NULL;
			SaveCopyInUndoList(Currentmodule);
			CopyMarkedItems(Currentmodule, GetScreen()->BlockLocate.m_MoveVector);
			break;

		case BLOCK_PASTE: /* Paste (recopie du dernier bloc sauve */
			GetScreen()->BlockLocate.m_BlockDrawStruct = NULL;
			break;

		case BLOCK_MIRROR_X:
		case BLOCK_MIRROR_Y:
		case BLOCK_INVERT:	/* Mirror by popup menu, from block move */
			SaveCopyInUndoList(Currentmodule);
			MirrorMarkedItems(Currentmodule, GetScreen()->BlockLocate.Centre());
			break;

		case BLOCK_ROTATE:
			SaveCopyInUndoList(Currentmodule);
			RotateMarkedItems(Currentmodule, GetScreen()->BlockLocate.Centre());
			break;

		case BLOCK_ZOOM:	// Handled by HandleBlockEnd
		case BLOCK_DELETE:
		case BLOCK_SAVE:
		case BLOCK_ABORT:
		default:
			break;
	}

	GetScreen()->SetModify();

	DrawPanel->ManageCurseur = NULL;
	DrawPanel->ForceCloseManageCurseur = NULL;
	GetScreen()->BlockLocate.m_Flags = 0;
	GetScreen()->BlockLocate.m_State = STATE_NO_BLOCK;
	GetScreen()->BlockLocate.m_Command =  BLOCK_IDLE;
	GetScreen()->m_CurrentItem = NULL;
	DrawPanel->Refresh(TRUE);

	SetToolID(m_ID_current_state, DrawPanel->m_PanelDefaultCursor, wxEmptyString );
}


/************************************************************************/
static void DrawMovingBlockOutlines(WinEDA_DrawPanel * panel, wxDC * DC,
	bool erase )
/************************************************************************/
/* Retrace le contour du block de recherche de structures
	L'ensemble du block suit le curseur
*/
{
DrawBlockStruct * PtBlock;
BASE_SCREEN * screen = panel->m_Parent->GetScreen();
EDA_BaseStruct * item;
wxPoint move_offset;
MODULE * Currentmodule = EDA_Appl->m_ModuleEditFrame->m_Pcb->m_Modules;
	
	PtBlock = &panel->GetScreen()->BlockLocate;
	GRSetDrawMode(DC, g_XorMode);

	/* Effacement ancien cadre */
	if( erase )
	{
		PtBlock->Offset(PtBlock->m_MoveVector);
		PtBlock->Draw(panel, DC);
		PtBlock->Offset( -PtBlock->m_MoveVector.x, -PtBlock->m_MoveVector.y);

		if ( Currentmodule )
		{	
			move_offset.x =  - PtBlock->m_MoveVector.x;
			move_offset.y =  - PtBlock->m_MoveVector.y;
			item = Currentmodule->m_Drawings;
			for ( ; item != NULL; item = item->Next() )
			{
				if ( item->m_Selected == 0 ) continue;
				switch( item->m_StructType )
				{
					case TYPETEXTEMODULE:
						((TEXTE_MODULE *) item)->Draw(panel, DC, move_offset, g_XorMode);
						break;
		
					case TYPEEDGEMODULE:
						((EDGE_MODULE *) item)->Draw(panel, DC, move_offset, g_XorMode);
						break;
		
					default: break;
				}
			}
			D_PAD* pad = Currentmodule->m_Pads;
			for ( ; pad != NULL; pad = pad->Next() )
			{
				if ( pad->m_Selected == 0 ) continue;
				pad->Draw(panel, DC, move_offset, g_XorMode);
			}
		}
	}

	/* Redessin nouvel affichage */
	PtBlock->m_MoveVector.x = screen->m_Curseur.x - PtBlock->m_BlockLastCursorPosition.x;
	PtBlock->m_MoveVector.y = screen->m_Curseur.y - PtBlock->m_BlockLastCursorPosition.y;

	GRSetDrawMode(DC, g_XorMode);
	PtBlock->Offset(PtBlock->m_MoveVector);
	PtBlock->Draw(panel, DC);
	PtBlock->Offset( -PtBlock->m_MoveVector.x, -PtBlock->m_MoveVector.y);

	
	if ( Currentmodule )
	{	
		item = Currentmodule->m_Drawings;
		move_offset.x =  - PtBlock->m_MoveVector.x;
		move_offset.y =  - PtBlock->m_MoveVector.y;
		for ( ; item != NULL; item = item->Next() )
		{
			if ( item->m_Selected == 0 ) continue;
			switch( item->m_StructType )
			{
				case TYPETEXTEMODULE:
					((TEXTE_MODULE *) item)->Draw(panel, DC, move_offset, g_XorMode);
					break;
	
				case TYPEEDGEMODULE:
					((EDGE_MODULE *) item)->Draw(panel, DC, move_offset, g_XorMode);
					break;
	
				default: break;
			}
		}
		D_PAD* pad = Currentmodule->m_Pads;
		for ( ; pad != NULL; pad = pad->Next() )
		{
			if ( pad->m_Selected == 0 ) continue;
			pad->Draw(panel, DC, move_offset, g_XorMode);
		}
	}
}

/****************************************************************************/
void CopyMarkedItems(MODULE *module, wxPoint offset)
/****************************************************************************/
/* Copy marked items, at new position = old position + offset 
*/
{
EDA_BaseStruct * item;
EDA_BaseStruct* NewStruct;
	
	if ( module == NULL ) return;
		
	D_PAD * pad = module->m_Pads;
	for ( ; pad != NULL; pad = pad->Next() )
	{
		if ( pad->m_Selected == 0 ) continue;
		pad->m_Selected = 0;
		D_PAD * NewPad = new D_PAD( module );
		NewPad->Copy(pad);
		NewPad->m_Selected = IS_SELECTED;
		NewPad->Pnext = module->m_Pads;
		NewPad->Pback = module;
		module->m_Pads->Pback = NewPad;
		module->m_Pads = NewPad;
  	}

	item = module->m_Drawings;
	for ( ; item != NULL; item = item->Next() )
	{
		if ( item->m_Selected == 0 ) continue;
		item->m_Selected = 0;
		NewStruct = NULL;
		switch(item->m_StructType)
		{
			case TYPETEXTEMODULE:
				NewStruct = new TEXTE_MODULE( module );
				((TEXTE_MODULE*)NewStruct)->Copy((TEXTE_MODULE*)item);
				break;

			case TYPEEDGEMODULE:
				NewStruct = new EDGE_MODULE( module );
				((EDGE_MODULE*)NewStruct)->Copy((EDGE_MODULE*)item);
				break;
			default:
				DisplayError(NULL, wxT("Internal Err: CopyMarkedItems: type indefini"));
				break;
		}
		if( NewStruct == NULL) break;
		NewStruct->m_Selected = IS_SELECTED;
		NewStruct->Pnext = module->m_Drawings;
		NewStruct->Pback = module;
		module->m_Drawings->Pback = module;
		module->m_Drawings = NewStruct;
  	}
	
	MoveMarkedItems(module, offset);
}

/****************************************************/
void MoveMarkedItems(MODULE *module, wxPoint offset)
/****************************************************/
/* Move marked items, at new position = old position + offset 
*/
{
EDA_BaseStruct * item;
	
	if ( module == NULL ) return;
		
	D_PAD * pad = module->m_Pads;
	for ( ; pad != NULL; pad = pad->Next() )
	{
		if ( pad->m_Selected == 0 ) continue;
		pad->m_Pos.x += offset.x;
		pad->m_Pos.y += offset.y;
		pad->m_Pos0.x += offset.x;
		pad->m_Pos0.y += offset.y;
	}

	item = module->m_Drawings;
	for ( ; item != NULL; item = item->Next() )
	{
		if ( item->m_Selected == 0 ) continue;
 
		switch ( item->m_StructType )
		{
			case TYPETEXTEMODULE:
				((TEXTE_MODULE*)item)->m_Pos.x += offset.x;
				((TEXTE_MODULE*)item)->m_Pos.y += offset.y;
				((TEXTE_MODULE*)item)->m_Pos0.x += offset.x;
				((TEXTE_MODULE*)item)->m_Pos0.y += offset.y;
				break;

			case TYPEEDGEMODULE :
				((EDGE_MODULE*)item)->m_Start.x += offset.x;
				((EDGE_MODULE*)item)->m_Start.y += offset.y;
				((EDGE_MODULE*)item)->m_End.x += offset.x;
				((EDGE_MODULE*)item)->m_End.y += offset.y;
				((EDGE_MODULE*)item)->m_Start0.x += offset.x;
				((EDGE_MODULE*)item)->m_Start0.y += offset.y;
				((EDGE_MODULE*)item)->m_End0.x += offset.x;
				((EDGE_MODULE*)item)->m_End0.y += offset.y;
				break;
		}
		item->m_Flags = item->m_Selected = 0;
 	}
}

/******************************************************/
void DeleteMarkedItems(MODULE *module)
/******************************************************/
/* Delete marked items 
*/
{
EDA_BaseStruct * item, * next_item;
D_PAD * pad, * next_pad;
	
	if ( module == NULL ) return;
		
	pad = module->m_Pads;
	for ( ; pad != NULL; pad = next_pad )
	{
		next_pad = pad->Next();
		if ( pad->m_Selected == 0 ) continue;
		DeleteStructure(pad);
	}

	item = module->m_Drawings;
	for ( ; item != NULL; item = next_item )
	{
		next_item = item->Next();
		if ( item->m_Selected == 0 ) continue;
		DeleteStructure(item);
	}
}


/******************************************************/
void MirrorMarkedItems(MODULE *module, wxPoint offset)
/******************************************************/
/* Mirror marked items, refer to a Vertical axis at position offset
*/
{
#define SETMIRROR(z) (z) -= offset.x; (z) = -(z); (z) += offset.x;
EDA_BaseStruct * item;
	
	if ( module == NULL ) return;
		
	D_PAD * pad = module->m_Pads;
	for ( ; pad != NULL; pad = pad->Next() )
	{
		if ( pad->m_Selected == 0 ) continue;
		SETMIRROR(pad->m_Pos.x);
		pad->m_Pos0.x = pad->m_Pos.x;
		pad->m_Offset.x = -pad->m_Offset.x;
		pad->m_DeltaSize.x = -pad->m_DeltaSize.x;
		pad->m_Orient = 1800 - pad->m_Orient;
		NORMALIZE_ANGLE(pad->m_Orient);
	}

	item = module->m_Drawings;
	for ( ; item != NULL; item = item->Next() )
	{
		if ( item->m_Selected == 0 ) continue;
 
		switch ( item->m_StructType )
		{
			case TYPEEDGEMODULE:
				SETMIRROR(((EDGE_MODULE*)item)->m_Start.x);
				((EDGE_MODULE*)item)->m_Start0.x = ((EDGE_MODULE*)item)->m_Start.x;
				SETMIRROR(((EDGE_MODULE*)item)->m_End.x);
				((EDGE_MODULE*)item)->m_End0.x = ((EDGE_MODULE*)item)->m_End.x;
				((EDGE_MODULE*)item)->m_Angle = - ((EDGE_MODULE*)item)->m_Angle;
				break;
	
			case TYPETEXTEMODULE:
				SETMIRROR(((TEXTE_MODULE*)item)->m_Pos.x);
				((TEXTE_MODULE*)item)->m_Pos0.x = ((TEXTE_MODULE*)item)->m_Pos.x;
				break;
		}
		item->m_Flags = item->m_Selected = 0;
 	}
}

/******************************************************/
void RotateMarkedItems(MODULE *module, wxPoint offset)
/******************************************************/
/* Rotate marked items, refer to a Vertical axis at position offset
*/
{
#define ROTATE(z) RotatePoint((&z), offset, 900)
EDA_BaseStruct * item;
	
	if ( module == NULL ) return;
		
	D_PAD * pad = module->m_Pads;
	for ( ; pad != NULL; pad = pad->Next() )
	{
		if ( pad->m_Selected == 0 ) continue;
		ROTATE(pad->m_Pos);
		pad->m_Pos0 = pad->m_Pos;
		pad->m_Orient += 900;
		NORMALIZE_ANGLE(pad->m_Orient);
	}

	item = module->m_Drawings;
	for ( ; item != NULL; item = item->Next() )
	{
		if ( item->m_Selected == 0 ) continue;
 
		switch ( item->m_StructType )
		{
			case TYPEEDGEMODULE:
				ROTATE(((EDGE_MODULE*)item)->m_Start);
				((EDGE_MODULE*)item)->m_Start0 = ((EDGE_MODULE*)item)->m_Start;
				ROTATE(((EDGE_MODULE*)item)->m_End);
				((EDGE_MODULE*)item)->m_End0 = ((EDGE_MODULE*)item)->m_End;
				break;
	
			case TYPETEXTEMODULE:
				ROTATE(((TEXTE_MODULE*)item)->m_Pos);
				((TEXTE_MODULE*)item)->m_Pos0 = ((TEXTE_MODULE*)item)->m_Pos;
				((TEXTE_MODULE*)item)->m_Orient += 900;
				break;
		}
		item->m_Flags = item->m_Selected = 0;
 	}
}

/*********************************************************/
void ClearMarkItems(MODULE * module)
/*********************************************************/
{
EDA_BaseStruct * item;
	
	if ( module == NULL ) return;

	item = module->m_Drawings;
	for ( ; item != NULL; item = item->Next() )
		item->m_Flags = item->m_Selected = 0;

	item = module->m_Pads;
	for ( ; item != NULL; item = item->Next() )
		item->m_Flags = item->m_Selected = 0;
}

/***************************************************************/
int MarkItemsInBloc(MODULE * module, EDA_Rect & Rect)
/***************************************************************/
/* Mark items inside rect.
	Items are inside rect when an end point is inside rect
*/
{
EDA_BaseStruct * item;
int ItemsCount = 0;
wxPoint pos;
D_PAD * pad;
	
	if ( module == NULL ) return 0;
		
	pad = module->m_Pads;
	for ( ; pad != NULL; pad = pad->Next() )
	{
		pad->m_Selected = 0;
		pos = pad->m_Pos;
		if ( Rect.Inside(pos) )
		{
			pad->m_Selected = IS_SELECTED;
			ItemsCount++;
		}
	}

	item = module->m_Drawings;
	for ( ; item != NULL; item = item->Next() )
	{
		item->m_Selected = 0;
		switch( item->m_StructType )
		{
			case TYPEEDGEMODULE:
				pos = ((EDGE_MODULE*)item)->m_Start;
				if ( Rect.Inside(pos) )
				{
					item->m_Selected = IS_SELECTED;
					ItemsCount++;
				}
				pos = ((EDGE_MODULE*)item)->m_End;
				if ( Rect.Inside(pos) )
				{
					item->m_Selected = IS_SELECTED;
					ItemsCount++;
				}
				break;
		
			case TYPETEXTEMODULE:
				pos = ((TEXTE_MODULE*)item)->m_Pos;
				if ( Rect.Inside(pos) )
				{
					item->m_Selected = IS_SELECTED;
					ItemsCount++;
				}
				break;
			
			default:
				break;
		}
		
	}
	return ItemsCount;
	
}
