#include"common.h"
#include"resource.h"
#include<CommCtrl.h>

#pragma comment(lib,"ComCtl32.lib")

#define ID_TIMER 100

extern void Proc_Paint(HWND hwnd,State state);	//描画

extern BOOL CALLBACK InputNumDlgProc(HWND hDlg,UINT msg,WPARAM wp,LPARAM lp);	//ゲートの入力数を入力
extern BOOL CALLBACK LabelDlgProc(HWND hDlg,UINT msg,WPARAM wp,LPARAM lp);	//ラベル名
extern BOOL CALLBACK AutoDlgProc(HWND hDlg,UINT msg,WPARAM wp,LPARAM lp);	//自動実行のデータを入力

extern HINSTANCE hInst;	//インスタンスハンドル

extern Direction AlignedDirection(vector2D center,vector2D end); //最も近い八方位
extern vector2D AlignAngle(vector2D center,vector2D end);	//八方位上の点に変換


extern bool SaveAs(HWND hwnd,Data d,char *szF,char *szFT);
extern bool Save(HWND hwnd);
extern bool LoadData();
extern bool Open(HWND hwnd);
extern bool LoadModul(HWND hwnd);

extern bool SaveSVG(HWND hwnd,Data d);


Data main_data;

bool data_modify=false;	//main_dataが更新されていればtrue

State state=scroll;		//現在選択されているモード

char gate_path[256]="";

//格子間隔
int GridInterval=16;


namespace Automatic
{
	int ClockInLabel=-1;		//クロックを入力するラベル(-1なら発振なし)
	int BreakOutLabel=-1;		//変化したらブレークする出力(-1ならブレークなし)
	bool BreakLabelOldState;	//旧状態
	unsigned int half_cycle=100;	//半周期
}

namespace FileIO
{
	char szFile[MAX_PATH]="";
	char szFileTitle[MAX_PATH]="";
}

namespace Interface
{
	POINT MouseCoordinates={0,0};	//マウス座標
	POINT Scroll_Coordinates={0,0};	//スクロール
	vector2D AlignedMouseCoordinates;	//GridInterval間隔で
	vector2D FirstAlignedMouseCoordinates;	//始点
	RECT ClientRect;	//クライアント領域
	bool Mouse_Drag;	//ドラッグ中ならtrue
}

namespace Place
{
	Direction NewDirection=RIGHT;	//新しい記号の方向
	char NewLabelName[SIZE_OF_LABEL_NAME]="";	//新しいラベルの名前
	unsigned int NewGateIn=1;				//新しい論理ゲートの入力数
	Operation NewGateOperation=NOT;	//新しい論理ゲートの演算
	Module NewModule;		//新しいモジュール
}

namespace Drawing
{
	HBITMAP hBmpOne,hBmpAnd,hBmpOr;
	HDC hDcOne,hDcAnd,hDcOr;
	HFONT hFontH,hFontV;	//水平と垂直のフォント
}


int Node::nBranch()		//枝の数を返す
{
	int count=0;
	for(int i=0;i<8;i++)count+=(branch[i].GetDeviceClass()!=no_device);
	return count;
}


//四捨五入
double round_d(double x)
{
	if(x>0.0)
		return floor(x + 0.5);
	else
		return -1.0 * floor(fabs(x) + 0.5);
}

vector2D POINT2vector2D(POINT p)	//座標変換
{
	vector2D v=vector2D(
	(int)round_d((p.x+(Interface::ClientRect.left-Interface::ClientRect.right)/2-Interface::Scroll_Coordinates.x)/(double)GridInterval),
	(int)round_d((p.y+(Interface::ClientRect.top-Interface::ClientRect.bottom)/2-Interface::Scroll_Coordinates.y)/(double)GridInterval));
	return v;
}

void Node::write_branch(Direction d,DeviceClassification dclass,int idx,int sidx)
{
	branch[d].SetDeviceClass(dclass);
	branch[d].SetIndexAndSubIndex(idx,sidx);
}

const int STRSIZE=511;

void Data::status(char *s)
{
	sprintf_s(s,STRSIZE,"ノード:%d ゲート:%d 導線:%d 入力:%d 出力:%d モジュール:%d (%d,%d) %s",node_data.size(),gate.size(),conductor.size(),input.size(),output.size(),module.size(),Interface::AlignedMouseCoordinates.MulGridInterval().x/GridInterval,Interface::AlignedMouseCoordinates.MulGridInterval().y/GridInterval,gate_path);
}

void Data::Clear()
{
	node_data.clear();
	gate.clear();
	conductor.clear();
	input.clear();
	output.clear();
	module.clear();
}

void Module::Clear()
{
	up.clear();
	right.clear();
	down.clear();
	left.clear();
	node_out.clear();
	array_in.clear();
	((Data*)this)->Clear();
}

void Data::NegateInIndex(int i)
{
	DeviceIndex sd;
	input[i].SetState(!input[i].GetState());
	if(node_data[input[i].nodeindex()].isAllRight())
	{
		sd.SetDeviceClass(label_input);
		sd.SetIndexAndSubIndex(i,-1);
		Operate(input[i].nodeindex(),sd);
	}
}

void Data::NegateInCoordinate()
{
	for(unsigned int i=0;i<input.size();i++)
		if(i!=Automatic::ClockInLabel && Interface::AlignedMouseCoordinates==GetNodeCoordinate(input[i].nodeindex())-DirectionVector[input[i].direction()])
			NegateInIndex(i);
}

void Data::JumpToSource()
{
	for(unsigned int i=0;i<output.size();i++)
		if(Interface::AlignedMouseCoordinates==GetNodeCoordinate(output[i].nodeindex())-DirectionVector[output[i].direction()])
			switch(output[i].GetSourceDevClass())
			{
			case label_input:
				Interface::Scroll_Coordinates=GetNodeCoordinate(input[output[i].GetSourceDevIndex()].nodeindex()).MulGridInterval();
				Interface::Scroll_Coordinates.x*=-1;
				Interface::Scroll_Coordinates.y*=-1;
				break;
			case gate_out:
				Interface::Scroll_Coordinates=GetNodeCoordinate(gate[output[i].GetSourceDevIndex()].NodeOutIndex()).MulGridInterval();
				Interface::Scroll_Coordinates.x*=-1;
				Interface::Scroll_Coordinates.y*=-1;
				break;
			case module_out:
				Interface::Scroll_Coordinates = GetNodeCoordinate(module[output[i].GetSourceDevIndex()].NodeOut(output[i].GetSourceDevSubIndex())).MulGridInterval();
				Interface::Scroll_Coordinates.x*=-1;
				Interface::Scroll_Coordinates.y*=-1;
				break;
			}

	for(unsigned int i=0;i<gate.size();i++)
		for(int j=0;j<gate[i].nIn();j++)
			if(Interface::AlignedMouseCoordinates==node_data[gate[i].NodeInIndex(j)].position())
				switch(gate[i].GetSourceDevClass(j))
				{
				case label_input:
					Interface::Scroll_Coordinates=GetNodeCoordinate(input[gate[i].GetSourceDevIndex(j)].nodeindex()).MulGridInterval();
					Interface::Scroll_Coordinates.x*=-1;
					Interface::Scroll_Coordinates.y*=-1;
					break;
				case gate_out:
					Interface::Scroll_Coordinates=GetNodeCoordinate(gate[gate[i].GetSourceDevIndex(j)].NodeOutIndex()).MulGridInterval();
					Interface::Scroll_Coordinates.x*=-1;
					Interface::Scroll_Coordinates.y*=-1;
					break;
				case module_out:
					Interface::Scroll_Coordinates = GetNodeCoordinate(module[gate[i].GetSourceDevIndex(j)].NodeOut(gate[i].GetSourceDevSubIndex(j))).MulGridInterval();
					Interface::Scroll_Coordinates.x*=-1;
					Interface::Scroll_Coordinates.y*=-1;
					break;
				}
	for(unsigned int i=0;i<module.size();i++)
		for(unsigned int j=0;j<module[i].nInput();j++)
			if(Interface::AlignedMouseCoordinates==node_data[module[i].NodeIn(j)].position())
			{
				switch(module[i].InArray(j).GetDeviceClass())
				{
				case label_input:
					Interface::Scroll_Coordinates=GetNodeCoordinate(input[module[i].InArray(j).GetDeviceIndex()].nodeindex()).MulGridInterval();
					Interface::Scroll_Coordinates.x*=-1;
					Interface::Scroll_Coordinates.y*=-1;
					break;
				case gate_out:
					Interface::Scroll_Coordinates=GetNodeCoordinate(gate[module[i].InArray(j).GetDeviceIndex()].NodeOutIndex()).MulGridInterval();
					Interface::Scroll_Coordinates.x*=-1;
					Interface::Scroll_Coordinates.y*=-1;
					break;
				case module_out:
					Interface::Scroll_Coordinates = GetNodeCoordinate(module[module[i].InArray(j).GetDeviceIndex()].NodeOut(module[i].InArray(j).GetDeviceSubIndex())).MulGridInterval();
					Interface::Scroll_Coordinates.x*=-1;
					Interface::Scroll_Coordinates.y*=-1;
					break;
				}
			}
}


//更新後の保存の確認
bool Confirm(HWND hwnd)
{
	int id;

	id=MessageBox(hwnd,"更新されています。\n保存しますか?","警告",MB_YESNOCANCEL | MB_ICONEXCLAMATION);
	if(id==IDYES)
	{
		return Save(hwnd);
	}
	if(id==IDNO) return true;
	return false;
}

bool Data::GetOutState(int i)
{
	switch(output[i].GetSourceDevClass())
	{
	case label_input:
		return input[output[i].GetSourceDevIndex()].GetState();
		break;
	case gate_out:
		return gate[output[i].GetSourceDevIndex()].GetState();
		break;
	case module_out:
		return module[output[i].GetSourceDevIndex()].GetState(output[i].GetSourceDevSubIndex());
		break;
	default:
		return 0;
	}
}

bool Data::ExpandModule(HWND hwnd)
{
	for(unsigned int i=0;i<module.size();i++)
	if((Interface::MouseCoordinates.x-(module[i].Center()+DirectionVector[LEFT]*(module[i].LeftMargin()+module[i].InternalHalfWidth())).toPOINT().x)>0 &&
		(Interface::MouseCoordinates.y-(module[i].Center()+DirectionVector[UP]*(module[i].UpMargin()+module[i].InternalHalfHeight())).toPOINT().y)>0 &&
		(Interface::MouseCoordinates.x-(module[i].Center()+DirectionVector[RIGHT]*(module[i].RightMargin()+module[i].InternalHalfWidth())).toPOINT().x)<0 &&
		(Interface::MouseCoordinates.y-(module[i].Center()+DirectionVector[DOWN]*(module[i].DownMargin()+module[i].InternalHalfHeight())).toPOINT().y)<0)
		{
			char szFiletmp[MAX_PATH]="";
			char szFileTitletmp[MAX_PATH]="";
			SaveAs(hwnd,module[i],szFiletmp,szFileTitletmp);
		}
	return false;
}


//ウインドウプロシージャ
LRESULT CALLBACK WindowProc(HWND hwnd,UINT uMsg,WPARAM wParam,LPARAM lParam)
{
	const UINT MyStatus=100;
	static HWND hStatus;
	INITCOMMONCONTROLSEX ic;
	HDC hdc;
	vector2D OldAlignedMouseCoordinates=vector2D(0,0);
	char str[STRSIZE+1];
	//ウインドウメッセージ
	switch(uMsg)
	{
	case WM_CLOSE:
		state=scroll;
		if(data_modify)
		{
			if(Confirm(hwnd)) DestroyWindow(hwnd);
		}
		else
			DestroyWindow(hwnd);
		break;
	case WM_COMMAND:
		Automatic::ClockInLabel=-1;
		switch(LOWORD(wParam))
		{
		case IDM_EXIT:
			state=scroll;
			if(data_modify)
			{
				if(Confirm(hwnd)) DestroyWindow(hwnd);
			}
			else
				DestroyWindow(hwnd);
			break;
		case IDM_SVG:
			SaveSVG(hwnd,main_data);
			state=scroll;
			break;
		case IDM_NEW:
			if(data_modify)
			{
				if(Confirm(hwnd))
				{
					main_data.Clear();
					strcpy_s(FileIO::szFile,"");
					strcpy_s(FileIO::szFileTitle,"");
					InvalidateRect(hwnd,NULL,false);
					data_modify=false;
					Interface::Scroll_Coordinates.x=0;
					Interface::Scroll_Coordinates.y=0;
				}
			}
			else
			{
				main_data.Clear();
				strcpy_s(FileIO::szFile,"");
				strcpy_s(FileIO::szFileTitle,"");
				InvalidateRect(hwnd,NULL,false);
				data_modify=false;
				Interface::Scroll_Coordinates.x=0;
				Interface::Scroll_Coordinates.y=0;
			}
			state=scroll;
			break;
		case IDM_SAVEAS:
			if(SaveAs(hwnd,main_data,FileIO::szFile,FileIO::szFileTitle))data_modify=false;
			state=scroll;
			break;
		case IDM_SAVE:
			if(Save(hwnd))data_modify=false;
			state=scroll;
			break;
		case IDM_OPEN:
			if(data_modify)
			{
				if(Confirm(hwnd))
					if(Open(hwnd))
					{
						data_modify=false;
						Interface::Scroll_Coordinates.x=0;
						Interface::Scroll_Coordinates.y=0;
					}
			}
			else
				if(Open(hwnd))
				{
					data_modify=false;
					Interface::Scroll_Coordinates.x=0;
					Interface::Scroll_Coordinates.y=0;
				}
			state=scroll;
			break;
		case IDM_SCROLL:
			state=scroll;
			break;
		case IDM_MODULE_LOAD:
			if(LoadModul(hwnd))state=placemodule;
			break;
		case IDM_MODULE_SAVE:
			state=expandmodule;
			break;
		case IDM_IN_LABEL:
			state=placein;
			break;
		case IDM_OUT_LABEL:
			state=placeout;
			break;
		case IDM_CONDUCTOR:
			state=placewire;
			break;
		case IDM_NOT:
			Place::NewGateOperation=NOT;
			Place::NewGateIn=1;
			state=placegate;
			break;
		case IDM_OR:
			Place::NewGateOperation=OR;
			Place::NewGateIn=2;
			state=placegate;
			break;
		case IDM_AND:
			Place::NewGateOperation=AND;
			Place::NewGateIn=2;
			state=placegate;
			break;
		case IDM_NOR:
			Place::NewGateOperation=NOR;
			Place::NewGateIn=2;
			state=placegate;
			break;
		case IDM_NAND:
			Place::NewGateOperation=NAND;
			Place::NewGateIn=2;
			state=placegate;
			break;
		case IDM_N_GATEIN:
			state = DialogBox(hInst,TEXT("INPUTNUMDLG"),hwnd,(DLGPROC)InputNumDlgProc)==IDOK ? placegate : scroll;
			break;
		case IDM_ERASE:
			state=erase;
			break;
		case IDM_AUTO:
			DialogBox(hInst,TEXT("AUTODLG"),hwnd,(DLGPROC)AutoDlgProc);
			if(Automatic::ClockInLabel>=0)
			{
				SetTimer(hwnd,ID_TIMER,Automatic::half_cycle,NULL);
				if(Automatic::BreakOutLabel>=0)
					Automatic::BreakLabelOldState=main_data.GetOutState(Automatic::BreakOutLabel);
			}
			state=scroll;
			break;
		case IDM_VERSION:
			ShellAbout(hwnd,"論理回路シミュレータAst#論理回路シミュレータAst Ver0.5","岡　道雄\noka_mitio@yahoo.co.jp",NULL);
			state=scroll;
			break;
		}
		break;
	case WM_MOUSEMOVE:
		if((wParam & MK_LBUTTON)==0)Interface::Mouse_Drag=false;
		if(state==scroll && Interface::Mouse_Drag)
		{
			Interface::Scroll_Coordinates.x+=LOWORD(lParam)-Interface::MouseCoordinates.x;
			Interface::Scroll_Coordinates.y+=HIWORD(lParam)-Interface::MouseCoordinates.y;
			InvalidateRect(hwnd,NULL,true);
		}
		Interface::MouseCoordinates.x=LOWORD(lParam);
		Interface::MouseCoordinates.y=HIWORD(lParam);
		OldAlignedMouseCoordinates=Interface::AlignedMouseCoordinates;
		Interface::AlignedMouseCoordinates=POINT2vector2D(Interface::MouseCoordinates);
		if(Interface::AlignedMouseCoordinates!=OldAlignedMouseCoordinates)InvalidateRect(hwnd,NULL,true);
		break;
	case WM_RBUTTONDOWN:
		switch(Place::NewDirection)
		{
		case UP:
			Place::NewDirection=RIGHT;
			break;
		case RIGHT:
			Place::NewDirection=DOWN;
			break;
		case DOWN:
			Place::NewDirection=LEFT;
			break;
		case LEFT:
			Place::NewDirection=UP;
			break;
		}
		InvalidateRect(hwnd,NULL,false);
		break;
	case WM_LBUTTONDOWN:
		Interface::Mouse_Drag=true;
		Interface::FirstAlignedMouseCoordinates=POINT2vector2D(Interface::MouseCoordinates);
		switch(state)
		{
		case placewire:
			Interface::MouseCoordinates.x=LOWORD(lParam);
			Interface::MouseCoordinates.y=HIWORD(lParam);
			break;
		case placegate:
			if(main_data.AbleToPlaceGate()) main_data.PlaceGate(),data_modify=true;
			break;
		case placemodule:
			if(main_data.AbleToPlaceModule()) main_data.PlaceModule(),data_modify=true;
			break;
		case expandmodule:
			main_data.ExpandModule(hwnd);
			break;
		case placein:
			if(main_data.AbleToPlaceLabel())
				if(DialogBox(hInst,TEXT("LABELDLG"),hwnd,(DLGPROC)LabelDlgProc)==IDOK)
					main_data.PlaceInLabel(),data_modify=true;
			break;
		case placeout:
			if(main_data.AbleToPlaceLabel())
				if(DialogBox(hInst,TEXT("LABELDLG"),hwnd,(DLGPROC)LabelDlgProc)==IDOK)
					main_data.PlaceOutLabel(),data_modify=true;
			break;
		}
		InvalidateRect(hwnd,NULL,false);
		break;
	case WM_LBUTTONUP:
		Interface::Mouse_Drag=false;
		switch(state)
		{
		case placewire:
			if(Interface::FirstAlignedMouseCoordinates!=Interface::AlignedMouseCoordinates)
				if(main_data.AbleToPlaceConductor())
					main_data.PlaceConductor(),data_modify=true;
			break;
		case erase:
			if(main_data.EraseDevice())data_modify=true;
			break;
		case scroll:
			if(Interface::FirstAlignedMouseCoordinates==Interface::AlignedMouseCoordinates)
				main_data.NegateInCoordinate();
			break;
		}
		InvalidateRect(hwnd,NULL,false);
		break;
	case WM_LBUTTONDBLCLK:
			if(Interface::FirstAlignedMouseCoordinates==Interface::AlignedMouseCoordinates)
				main_data.JumpToSource();
		break;
	case WM_MOUSEWHEEL:
		if((short)(wParam >> 16)>0 && GridInterval<20)
		{
			Interface::Scroll_Coordinates.x=Interface::Scroll_Coordinates.x*(GridInterval+1)/GridInterval;
			Interface::Scroll_Coordinates.y=Interface::Scroll_Coordinates.y*(GridInterval+1)/GridInterval;
			GridInterval++;
		}
		if((short)(wParam >> 16)<0 && GridInterval>8)
		{
			Interface::Scroll_Coordinates.x=Interface::Scroll_Coordinates.x*(GridInterval-1)/GridInterval;
			Interface::Scroll_Coordinates.y=Interface::Scroll_Coordinates.y*(GridInterval-1)/GridInterval;
			GridInterval--;
		}
		DeleteObject(Drawing::hFontV);
		DeleteObject(Drawing::hFontH);
		Drawing::hFontH=CreateFont(GridInterval,GridInterval/2-1,0  ,0,FW_THIN,false,false,false,SHIFTJIS_CHARSET,OUT_DEFAULT_PRECIS,CLIP_DEFAULT_PRECIS,DEFAULT_QUALITY,DEFAULT_PITCH|FF_DONTCARE,"ＭＳ 明朝");
		Drawing::hFontV=CreateFont(GridInterval,GridInterval/2-1,-900,0,FW_REGULAR,false,false,false,SHIFTJIS_CHARSET,OUT_DEFAULT_PRECIS,CLIP_DEFAULT_PRECIS,PROOF_QUALITY,FIXED_PITCH|FF_MODERN,"ＭＳ 明朝");
		InvalidateRect(hwnd,NULL,false);
		break;
	case WM_KEYDOWN:
		break;
	case WM_TIMER:
		if(Automatic::BreakOutLabel>=0)
			if(main_data.GetOutState(Automatic::BreakOutLabel)!=Automatic::BreakLabelOldState) Automatic::ClockInLabel=-1;
		if(Automatic::ClockInLabel>=0)
		{
			main_data.NegateInIndex(Automatic::ClockInLabel);
			hdc=GetDC(hwnd);
			main_data.DrawInputState(hdc);
			main_data.DrawOutputState(hdc);
			ReleaseDC(hwnd,hdc);
		}
		else
			KillTimer(hwnd,ID_TIMER);
		break;
	case WM_PAINT:
		Proc_Paint(hwnd,state);
		main_data.status(str);
		SendMessage(hStatus,SB_SETTEXT,0,(LPARAM)str);
		if(strlen(FileIO::szFileTitle)>0)
			SetWindowText(hwnd,FileIO::szFileTitle);
		else
			SetWindowText(hwnd,"無題");
		break;
	case WM_SIZE:
		GetClientRect(hwnd,&Interface::ClientRect);
		SendMessage(hStatus,uMsg,wParam,lParam);
		break;
	case WM_CREATE:
		state=scroll;
		Interface::Mouse_Drag=false;
		GetClientRect(hwnd,&Interface::ClientRect);

		hdc=GetDC(hwnd);
		Drawing::hDcOne=CreateCompatibleDC(hdc);
		Drawing::hDcAnd=CreateCompatibleDC(hdc);
		Drawing::hDcOr=CreateCompatibleDC(hdc);
		ReleaseDC(hwnd,hdc);

		Drawing::hBmpOne=LoadBitmap(hInst,TEXT("BMP_ONE"));
		Drawing::hBmpAnd=LoadBitmap(hInst,TEXT("BMP_AND"));
		Drawing::hBmpOr =LoadBitmap(hInst,TEXT("BMP_OR"));

		SelectObject(Drawing::hDcOne,Drawing::hBmpOne);
		SelectObject(Drawing::hDcAnd,Drawing::hBmpAnd);
		SelectObject(Drawing::hDcOr,Drawing::hBmpOr);

		Drawing::hFontH=CreateFont(GridInterval,GridInterval/2-1,0  ,0,FW_THIN,false,false,false,SHIFTJIS_CHARSET,OUT_DEFAULT_PRECIS,CLIP_DEFAULT_PRECIS,DEFAULT_QUALITY,DEFAULT_PITCH|FF_DONTCARE,"ＭＳ 明朝");
		Drawing::hFontV=CreateFont(GridInterval,GridInterval/2-1,-900,0,FW_REGULAR,false,false,false,SHIFTJIS_CHARSET,OUT_DEFAULT_PRECIS,CLIP_DEFAULT_PRECIS,PROOF_QUALITY,FIXED_PITCH|FF_MODERN,"ＭＳ 明朝");

		ic.dwICC=ICC_BAR_CLASSES;
		ic.dwSize=sizeof(INITCOMMONCONTROLSEX);
		InitCommonControlsEx(&ic);
		hStatus=CreateStatusWindow(WS_CHILD|SBARS_SIZEGRIP|CCS_BOTTOM|WS_VISIBLE,"OK",hwnd,MyStatus);

		break;
	case WM_DESTROY:
		DestroyWindow(hStatus);
		DeleteObject(Drawing::hFontV);
		DeleteObject(Drawing::hFontH);
		DeleteDC(Drawing::hDcOr);
		DeleteDC(Drawing::hDcAnd);
		DeleteDC(Drawing::hDcOne);
		DeleteObject(Drawing::hBmpOr);
		DeleteObject(Drawing::hBmpAnd);
		DeleteObject(Drawing::hBmpOne);
		//終了

		PostQuitMessage(0);
		break;
	default:
		return DefWindowProc(hwnd,uMsg,wParam,lParam);
		break;
	}
	return 0;
}