implementation module EdProgramState;

/*	Access functions on the programstate */

import StdClass, StdInt, StdString, StdChar, StdBool,StdArray, StdTuple, StdList;
from StdFile import ::Files;
import deltaFont;
import EdCleanSystem;

import EdTypes, EdConstants, EdText, EdProject, EdPath, EdLists;

//	The IOState and program state
::	* ProgIO		:== (!ProgState,!IO);
::	* IO			:==	IOState ProgState;
::	* ProgState		= {editor :: !Editor};
::	CallBackFun		:== Bool -> ProgState -> * (IO -> ProgIO);

//	The local program state
::	 Editor		= 	{	defaults	:: !Defaults,
							startupinfo	:: !StartupInfo,
							clipboard	:: !Clipboard,
							findinfo	:: !FindInfo,
							findidinfo	:: !FindIdentInfo,
							editwdids	:: !EditWdIds,
							editwindows	:: !EditWindows,
							execwdid	:: !ExecWdId,
							project		:: !Project,
							callback	:: !CallBackFun };
	//	the default options, the start up directory of the clean programming env., the clipboard
	//	the search info, the search definition/implementation info, the windows ids, the windows,
	//	the currently active icl window id, the project, function to resume a 'search' or 'bring
	//	project up to date' operation.

::	StartupInfo =	{
						startupdir	:: !Pathname,
						linker_file_name :: !String,
						linker_begin_object_files :: ![String],
						linker_libraries :: ![String],
						linker_end_object_files :: ![String],
						assembler_file_name :: !String
					};

/*	Default values */

DefIgnoreCase				:== False;
DefBackwards				:== False;
DefWrapAround				:== True;
DefMatchWords				:== False;
DefSearchKind :== Implementation;
DefScan :== SearchImports;
DefExportsOnly :== False;
DefFindVerbose				:== True;
DefTabWidth					:== 4;
DefAutoIndent				:== True;
DefNewLines					:== HostNativeNewlineConvention;

DefCompilerNeverMemoryProfile	:== False;
DefCompilerNeverTimeProfile		:== False;
DefStrictnessAnalysis		:== True;
DefTypes					:== NoTypes;
DefAttr						:== False;
DefWarnings					:== False;
DefVerbose					:== False;
DefComment					:== False;
DefReuseUniqueNodes			:== False;

DefCheckStacks				:== False;
DefCheckIndices				:== False;
DefKeepABCFile				:== False;
DefTargetProcessor			:== CurrentProcessor;

DefHeapSize					:== 409600;
DefStackSize				:== 102400;
DefExtraMem					:== 81920;
DefaultHeapSizeMultiple		:== 4096/*16*256*/;
DefaultInitialHeapSize :== 204800;
DefShowExecTime				:== False;
DefShowGCs					:== False;
DefPrintStackSize			:== False;
DefOutput					:== ShowConstructors;
DefProfile					:== False;
DefProfile601				:== False;
DefMemoryProfile			:== False;
DefMemoryProfileMinimumHeapSize :== 0;
DefWriteStderrToFile		:== False;

DefBeVerbose				:== False;

/*	initial cursor blinking rate (will be ignored) */
							
	InitClipboard	:== Nil;
	InitFindInfo	:== {	find	= "",
							replace	= "",
							ignore	= DefIgnoreCase,
							backw	= DefBackwards,
							wrapa	= DefWrapAround,
							matchw	= DefMatchWords };
	InitFindIdInfo	:==	{	search_kind = DefSearchKind,
							cleanid	= "",
							imp		= DefScan,
							export_	= DefExportsOnly,
							verbose	= DefFindVerbose };
	InitWdIds		:== {wdids = Nil, curid = FirstEditWdID};
	InitEditWds		:== Nil;
	InitMainWd		:== NoWdID;

//	The initial call back function

InitCallBack :: !Bool !ProgState !IO -> ProgIO;
InitCallBack interrupt prog io = (prog, io);

//	The initial programstate.

InitEditor :: !{#Char} !*Files -> (!Editor,!*Files);
InitEditor fullApplicationPath files
	# (startupinfo,files) = ReadStartupInfo fullApplicationPath files;
	= let {
	defs			= {		defaultCompilerOptions
									  = DefCompilerOptions,
							cgo       = DefCodeGenOptions,
							ao	      = DefApplicationOptions,
							po	      = DefProjectOptions,
							paths     = DefPaths,
							linkOptions
									  = DefaultLinkOptions,
							edit      = editdefs,
							clip      = editdefs,
							errors	  = editdefs,
							types     = editdefs,
							dproject  = editdefs };
	editdefs		= {		eo			= {	tabs		= DefTabWidth,
											fontname	= default_font_name,
											fontsize	= default_font_size,
											autoi		= DefAutoIndent,
											newlines	= DefNewLines},
							pos_size	= DefWindowPos_and_Size };
//	default_font_name="Monaco";
//	default_font_size=9;
	(default_font_name,_,default_font_size)		= DefaultFont;
	} in
	  ({defaults	= defs,
		startupinfo	= startupinfo,
		clipboard	= InitClipboard,
		findinfo	= InitFindInfo,
		findidinfo	= InitFindIdInfo,
		editwdids	= InitWdIds,
		editwindows	= Nil,
		execwdid	= InitMainWd,
		project		= PR_InitProject,
		callback	= InitCallBack
		},files);

DefaultLinkOptions :: LinkOptions;
DefaultLinkOptions
	=	{
			useDefaultSystemObjects = True,
			useDefaultLibraries = True,
			extraObjectModules = Nil,
			libraries = Nil
		};

DefCompilerOptions :: CompilerOptions;
DefCompilerOptions = {	neverMemoryProfile = DefCompilerNeverMemoryProfile,
						neverTimeProfile	= DefCompilerNeverTimeProfile,
						sa		= DefStrictnessAnalysis,
						listTypes
								= DefTypes,
						attr	= DefAttr,
						gw		= DefWarnings,
						bv		= DefVerbose,
						gc		= DefComment,
						reuseUniqueNodes = DefReuseUniqueNodes };

DefCodeGenOptions :: CodeGenOptions;
DefCodeGenOptions = {	cs		= DefCheckStacks,
						ci		= DefCheckIndices,
						kaf		= DefKeepABCFile,
						tp		= DefTargetProcessor
					};
	
DefApplicationOptions :: ApplicationOptions;
DefApplicationOptions = {
		hs	= DefHeapSize,
		ss	= DefStackSize,
		em	= DefExtraMem,
		heap_size_multiple = DefaultHeapSizeMultiple,
		initial_heap_size = DefaultInitialHeapSize,
		set	= DefShowExecTime,
		sgc	= DefShowGCs,
		pss	= DefPrintStackSize,
		marking_collection = False,
		o	= DefOutput,
		fn	= fn,
		fs	= fs,
		write_stderr_to_file
						= DefWriteStderrToFile,
		memoryProfiling = DefMemoryProfile,
		memoryProfilingMinimumHeapSize = DefMemoryProfileMinimumHeapSize,
		profiling601 = DefProfile601,
		profiling = DefProfile
	};
	where {
		(fn,_,fs)	= EditorDefaultFont;
	};

DefProjectOptions :: ProjectOptions;
DefProjectOptions = {ProjectOptions | verbose = DefBeVerbose};
		
//	Window id operations

GetUsedWdIds :: !EditWindows -> List EditWdId;
GetUsedWdIds Nil			= Nil;
GetUsedWdIds ((id,wd):!wds)	= id :! GetUsedWdIds wds;
	
GetUsedWindows :: !EditWindows -> List EditWindow;
GetUsedWindows Nil				= Nil;
GetUsedWindows ((id,wd):!wds)	= wd :! GetUsedWindows wds;

//	EditWindow operations

GetFrontWindow :: !EditWindows -> (!EditWdId,!EditWindow);
GetFrontWindow ((id,active):!rest)
	= (id,active);
GetFrontWindow Nil
	= (NoWdID,EmptyWindow);

GetNextWindow	:: !EditWindows -> (!EditWdId,!EditWindow);
GetNextWindow ((id,active):!Nil)
	= (id,active);
GetNextWindow (cur:!(nextid,nextactive):!rest)
	= (nextid,nextactive);
GetNextWindow Nil
	= (NoWdID,EmptyWindow);

WindowsPresent :: !EditWindows -> Bool;
WindowsPresent Nil		= False;
WindowsPresent editwds	= True;
	
WindowPresent :: !EditWdId !EditWindows -> Bool;
WindowPresent wdid Nil	=  False;
WindowPresent wdid ((id,window):!rest)
	| wdid == id	=  True;
					=  WindowPresent wdid rest;

SetFrontWindow :: !EditWindow !EditWindows -> EditWindows;
SetFrontWindow window ((id,active):!rest)
	= (id,window):!rest;
SetFrontWindow window Nil
	= Nil;

RemoveFrontWindow :: !EditWindows !EditWdIds -> (!EditWindows,!EditWdIds);
RemoveFrontWindow editwindows=:((id,active):!rest) editwdids=:{wdids=ids}
	| id < FirstEditWdID
		= (rest,editwdids);
		= (rest,{editwdids & wdids = id:!ids});
RemoveFrontWindow Nil editwdids
	= (Nil, editwdids);
							
GetWindow :: !EditWdId !EditWindows -> EditWindow;
GetWindow wdid ((id,window):!wds)
		| wdid == id	= window;
						= GetWindow wdid wds;
GetWindow wdid Nil		= EmptyWindow;

SetWindow :: !EditWdId !EditWindow !EditWindows -> EditWindows;
SetWindow wdid window ((edit=:(id,old)):!rest)
		| wdid == id	= (id,window) :! rest;
						= edit :! SetWindow wdid window rest;
SetWindow wdid window Nil
						= Nil;
		
AddWindow :: !WdType !EditWindow !EditWindows !EditWdIds -> (!EditWindows,!EditWdIds,!EditWdId);
AddWindow wdtype window=:{wstate=pathname} editwindows editwdids
	= ((wdid,window):!editwindows,editwdids`,wdid);
	where {
	(editwdids`,wdid)	= case wdtype of
							{	EditWd		-> EWs_GetNewWdId editwdids;
								ClpbrdWd	-> (editwdids,ClpbrdWdID);
								ProjectWd	-> (editwdids,ProjectWdID);
								ErrorWd		-> (editwdids,ErrorWdID);
								TypeWd		-> (editwdids,TypeWdID);
							};
	};
	
EWs_GetNewWdId :: !EditWdIds -> (!EditWdIds,!EditWdId);
EWs_GetNewWdId editwdids=:{wdids=Nil,curid=n}
	= ({editwdids & curid = inc n}, inc n);
EWs_GetNewWdId editwdids=:{wdids=(wdid:!ids)}
	= ({editwdids & wdids = ids}, wdid);


SetWindowInFront :: !EditWdId !EditWindows -> EditWindows;
SetWindowInFront id Nil   = Nil;
SetWindowInFront id list  = Concat e r;
where 
{
	(e,r) = getit id list;

	getit id Nil = (Nil,Nil);
	getit id (first:!rest) 
		|  fst first == id = (first:!Nil, rest);
					   = (element, first :! newrest);
	where {	(element, newrest) = getit id rest; }
}

GetWindowIndex :: !String !EditWindows -> Int;
GetWindowIndex name editwindows
	=  EWs_GetWindowIndex 1 (DeCapitalize name) editwindows;

EWs_GetWindowIndex :: !Int !String !EditWindows -> Int;
EWs_GetWindowIndex index name ((id,wd=:{wstate={pathname}}) :! rest)
	|  DeCapitalize (RemovePath pathname) > name
		=  EWs_GetWindowIndex index name rest;
		=  EWs_GetWindowIndex (inc index) name rest;
EWs_GetWindowIndex index name Nil
		=  index;

IsExistingPathname	:: !Pathname !EditWindows -> (!Bool, !EditWdId);
IsExistingPathname path Nil =  (False, NoWdID);
IsExistingPathname path ((id,wd=:{wstate={pathname}}) :! rest)
	| path == pathname	= (True, id);
						= IsExistingPathname path rest;
						
NewExecWdId :: !EditWdId !EditWindows -> EditWdId;
NewExecWdId oldexecwdid Nil		= NoWdID;
NewExecWdId oldexecwdid ((id,wd=:{wstate={pathname}}) :! rest)
	| id == oldexecwdid			= id;
	| IsImpPathname pathname	= id;
								= NewExecWdId oldexecwdid rest;

//	Initializers

ZeroCursor		:: CursorPos;
ZeroCursor		= {vis = False,x = LinesLeft,y = PictureTop,u_d = LinesLeft};

EmptyCurLine	:: CurLine;
EmptyCurLine	= {CurLine | changed=False,before = Nil,after = Nil,lnr = 0,cnr = 0};

EmptyTSel		:: PartTSel;
EmptyTSel		= {l1=0, c1=0, l2=0, c2=0};

EmptySelection	:: Selection;
EmptySelection	= {	tsel	= EmptyTSel,
					psel	= {bx = 0, by = 0, ex = 0, ey = 0} };

EmptyUndo		:: UndoInfo;
EmptyUndo		= {	clipcopied	= EmptyClipCopied,
					removed		= EmptyRemoved,
					replaced	= EmptyReplaced,
					added		= EmptyAdded,
					menu		= EmptyUndoItem };

EmptyClipCopied	:: ClipCopied;
EmptyClipCopied	= {clipset=False,clipboard=Nil,pos={l1=(-1),c1=(-1),l2=(-1),c2=(-1)}};

EmptyRemoved	:: Removed;
EmptyRemoved	= {Removed |	before		= Nil,
								after		= Nil,
								lnr			= (-1),
								cnr			= (-1) };

EmptyReplaced	:: ReplacedAll;								
EmptyReplaced	= {len = 0,org = "", repl = "",ln_cns = Nil};

EmptyAdded		:: PartTSel;
EmptyAdded		= EmptyTSel;

EmptyUndoItem	:: UndoMenuItem;
EmptyUndoItem	= {undo = True, action = ""};

EmptyWindowUpdate :: WindowUpdate;
EmptyWindowUpdate	= {	added		= EmptyTSel,
						nrremoved	= 0,
						removed		= Nil,
						oldsel		= EmptySelection,
						selection	= EmptySelection,
						oldpos		= ZeroCursor,
						curpos		= ZeroCursor };

// Empty Window

EmptyWindow :: EditWindow;
EmptyWindow
	= {	wtext	= {	text		= EmptyText,
					nrlines		= EmptyNrLines,
					cursorpos	= ZeroCursor,
					curline		= EmptyCurLine,
					selection	= EmptySelection },
		wformat	= {	tabw		= EmptyTabWidth,
					autoi		= False,
					newlines	= HostNativeNewlineConvention,
					winfont		= empty_font,
					metrics		= EmptyMetrics },
		wstate	= {	undoinfo	= EmptyUndo,
					saved		= IsSaved,
					wdtype		= EditWd,
					pathname	= EmptyPathname,
					co			= DefaultCompilerOptions } };
	where {
	empty_font		= {fontname=ft,fontsize=sz,font=rfont};
	(_,rfont)		= SelectFont ft style sz;
	(ft,style,sz)	= EditorDefaultFont;
	};	
	
// Default Edit Options

DefaultEditOptions :: EditOptions;
DefaultEditOptions
	= {	tabs		= DefTabWidth,
		fontname	= ft,
		fontsize	= sz,
		autoi		= DefAutoIndent,
		newlines	= DefNewLines};
	where {
	(ft,_,sz)		= EditorDefaultFont;
	};

// Default Compiler Options

DefaultCompilerOptions	:: CompilerOptions;
DefaultCompilerOptions	= {	neverMemoryProfile = DefCompilerNeverMemoryProfile,
							neverTimeProfile		= DefCompilerNeverTimeProfile,
							sa			= DefStrictnessAnalysis,
							listTypes	= DefTypes,
							attr		= DefAttr,
							gw			= DefWarnings,
							bv			= DefVerbose,
							gc			= DefComment,
							reuseUniqueNodes	= DefReuseUniqueNodes };
									
DefaultABCOptions		:: ABCOptions;
DefaultABCOptions =	{		abcMemoryProfile 		= False,
							abcTimeProfile			= False,
							abcStrictnessAnalysis	= DefStrictnessAnalysis,
							abcGiveWarnings			= DefWarnings,
							abcBeVerbose			= DefVerbose,
							abcGenerateComments		= DefComment,
							abcReuseUniqueNodes 	= DefReuseUniqueNodes };

//	Return a fresh EditWindow.

NewEditWindow :: !WdType !EditOptions !CompilerOptions !Text !Pathname !NrLines -> EditWindow;
NewEditWindow wdtype eo co text pathname nrlines
	= {	wtext	= {	text		= text,
					nrlines		= nrlines,
					cursorpos	= ZeroCursor,
					curline		= EmptyCurLine,
					selection	= EmptySelection },
		wformat	= {	tabw		= tabw,
					autoi		= autoi,
					newlines	= newlines,
					winfont		= font,
					metrics		= metrics },
		wstate	= {	undoinfo	= EmptyUndo,
					saved		= IsSaved,
					wdtype		= wdtype,
					pathname	= pathname,
					co			= co }};
	where {
	(tabw,font,metrics,autoi,newlines)	= GetDefaultValues eo;
	};

GetDefaultValues :: !EditOptions -> (!TabWidth,!WindowFont,!FontMetr,!Bool,!NewlineConvention);
GetDefaultValues {tabs,fontname,fontsize,autoi,newlines}
	=  (	{ttabw=tabs,ptabw=tabw},
			{fontname=rfontName,fontsize=rfontSize,font=rfont},
			{ascent=at_new,descent=dt,lead=ld,height=ht},
			autoi,
			newlines );
	where {
	tabw				= tabs *  FontCharWidth ' ' rfont ;

	(rfontName, rfontSize, rfont)
		=	EditorGetFont fontname fontsize;
	ht					= at_new + dt + ld;
	(at_new,dt,_,ld)	= FontMetrics rfont;
	};

EditorFontNames :: [FontName];
EditorFontNames
	=:	FontNames;

EditorGetFont :: !FontName !FontSize -> (!FontName, !FontSize, !Font);
EditorGetFont fontName fontSize
	| available
		=	(fontName, fontSize, font);
	// otherwise
		=	(defaultFontName, defaultFontSize, defaultFont);
	where
	{
		/*	Stupid Windows implementation of SelectFont always returns True, so we test
			by comparing to FontNames. For the same reason we can't test the font size and
			assume that scaling will be used.
		*/
		available
			=	isMember fontName EditorFontNames;
		(_, font)
			=	SelectFont fontName defaultStyle fontSize;
		(_, defaultFont)
			=	SelectFont defaultFontName defaultStyle defaultFontSize;
		(defaultFontName,defaultStyle,defaultFontSize)
//			=	EditorDefaultFont;
			=	(f (filter (\(name,_,_) -> isMember name EditorFontNames) EditorDefaultFontsToTry));
			where
			{
				f []
					=	DefaultFont;
				f [fontInfo : _]
					=	fontInfo;
			};
	};
//	Window operations

EW_GetEditOptions :: !EditWindow -> EditOptions;
EW_GetEditOptions wd=:{wformat={tabw={ttabw},autoi,newlines,winfont={WindowFont | fontname,fontsize}}}
	= {tabs = ttabw, autoi = autoi, fontname = fontname, fontsize = fontsize, newlines = newlines};
	
IsEmptySelection :: !Selection -> Bool;
IsEmptySelection sel=:{tsel={l1,c1,l2,c2},psel} = l1 == l2  &&  c1 == c2 ;
	
EW_GetTextPosition :: !CurLine !Selection -> PartTSel;
EW_GetTextPosition {CurLine | lnr,cnr} {tsel=sel=:{l1,c1,l2,c2}}
	| l1 == l2 && c1 == c2	= {l1 = lnr, c1 = cnr, l2 = lnr, c2 = cnr};
							= sel;

EW_GetActivePos	:: !CurLine !CursorPos !Selection -> ActPasPos;
EW_GetActivePos	curline		=: {CurLine | lnr,cnr}
				cursorpos	=: {CursorPos | x,y}
				selection	=: {	tsel	= {l1,c1,l2,c2},
									psel	= {bx,by,ex,ey} }
	| l1 == l2	&& c1 == c2	= {ActPasPos | lnr=lnr,cnr=cnr,x=x,y=y};
	| bx == x	&& by == y	= {ActPasPos | lnr=l1,cnr=c1,x=bx,y=by};
							= {ActPasPos | lnr=l2,cnr=c2,x=ex,y=ey};

EW_GetPassivePos :: !CurLine !CursorPos !Selection -> ActPasPos;
EW_GetPassivePos	curline		=: {CurLine | lnr,cnr}
					cursorpos	=: {CursorPos | x,y}
					selection	=: {	tsel	= {l1,c1,l2,c2},
										psel	= {bx,by,ex,ey} }
	| l1 == l2	&& c1 == c2	= {ActPasPos | lnr=lnr,cnr=cnr,x=x,y=y};
	| bx == x	&& by == y	= {ActPasPos | lnr=l2,cnr=c2,x=ex,y=ey};
							= {ActPasPos | lnr=l1,cnr=c1,x=bx,y=by};

EW_SetEditOptions :: !EditOptions !EditWindow -> EditWindow;
EW_SetEditOptions new wd
	= {wd & wformat = {tabw = tabw, winfont = font, metrics = metrics, autoi = autoi, newlines = newlines}}; 
	where {
	(tabw,font,metrics,autoi,newlines)	= GetDefaultValues new;

//	(rfont, rfontName, rfontSize)
//		= EditorGetFont fontName size;
	};

EW_ResetCurLine	:: !Bool !Text !CurLine -> (!Bool,!Text,!CurLine);
EW_ResetCurLine same_line text curline=:{changed,before,after,lnr}
	| do_reset 	= (True,Text_SetLine lnr (Line_GlueLine before after) text,{curline & changed=False});
				= (False,text,curline);
	where {
	do_reset = not same_line && changed;
	};								
/*	Miscellaneous functions */

DeCapitalize :: !String -> String;
DeCapitalize str =  DeCap (dec (size str)) ( toInt FirstSmall  -  toInt FirstCapital ) str;

DeCap :: !Int !Int !String -> String;
DeCap i diff str
	| i < 0
		=  str;
	| char < FirstCapital || char > LastCapital
		=  DeCap (dec i) diff str;
		=  DeCap (dec i) diff (str := (i, toChar ( toInt char  + diff)));
	where {
	char= str .[ i];
	}; 

Between	:: !Int !Int !Int -> Int;
Between l h x	| x < l =  l;
	            | x > h =  h;
	              		=  x;
	              		
IsSaved			:== True;
EmptyTabWidth	:== {ttabw = 0,ptabw = 0};
EmptyMetrics	:== {ascent = 0, descent = 0, lead = 0, height = 0};
EmptyNrLines	:== 0;


DummyWindowPos :: WindowPos_and_Size;
DummyWindowPos =
	 {	posx	= 0,
		posy	= 0,
		sizex	= 0,
		sizey	= 0};

DummyModInfo :: ModInfo;
DummyModInfo =
	{	dir		= "",
		compilerOptions = DefCompilerOptions,
		defeo 	= {eo=DefaultEditOptions, pos_size=DummyWindowPos},
		impeo	= {eo=DefaultEditOptions, pos_size=DummyWindowPos},
		defopen = False,
		impopen = False,
		date 	= NoDate,
		deps 	= Nil,
		abcLinkInfo = { linkObjFileNames=Nil, linkLibraryNames=Nil }	// MW++
	};

EditorDefaultFont :: (!FontName, ![FontStyle], !FontSize);
EditorDefaultFont
	=	DefaultFont;
