definition module iTasks.UI.Editor
/**
* This module defines the interfaces for task editors used in the interact task
* the interact core task uses these editors to generate and update the user interface
*/

from StdGeneric import generic binumap

from ABC.Interpreter import :: PrelinkedInterpretationEnvironment
from ABC.Interpreter.JavaScript import :: JSWorld, :: JSVal
from iTasks.UI.Definition import :: UI, :: UIAttributes, :: UIChange, :: UIAttributeChange, :: TaskId

from iTasks.Engine import :: EngineOptions
from iTasks.Internal.IWorld import :: IWorld
from iTasks.Internal.Generic.Defaults import generic gDefault
from Data.Either import :: Either
from Data.Map import :: Map
from Data.Error import :: MaybeError, :: MaybeErrorString
from Data.Functor import class Functor
from Text.GenJSON import :: JSONNode, generic JSONEncode, generic JSONDecode
from Text.GenPrint import generic gPrint, class PrintOutput, :: PrintState
from StdOverloaded import class toString, class fromString, class zero
from Control.GenBimap import generic bimap
from Control.Applicative import class pure, class <*>, class Applicative
from Control.Monad import class Monad

/**
 * Definition of an editor.
 *
 * @var r The type of data that the editor reads (when displaying or updating)
 * @var w The type of data that the editor writes back after an event (when entering or updating)
 *
 *        For many editors, including the generic editor, `w = ?r` because while editing a value
 *        it can become temporary unavailable which is indicated by writing `?None`.
 *
 *        Some editors are guaranteed to always have a value (e.g. a slider of type `Int`).
 *        In those cases `w = r`.
 *
 *        Additionally, to reduce communication, editors can use encoded partial values for reading
 *        and writing.
 */
:: Editor r w =
	{ onReset :: !UIAttributes (?r) -> *(*VSt ->
		*(*MaybeErrorString *(UI, EditState, ?w), *VSt)) //...
		//* Generating the initial UI
		//* The r value is optional. If no value is given, an empty editor is generated
	, onEdit :: (EditorId, JSONNode) EditState *VSt ->
		*(*MaybeErrorString *(?(UIChange, EditState, ?w)), *VSt) //...
		//* React to edit events (or determine that the event is not for this editor)
		//* When the results is `?None`: the event is not for this editor or its children
		//* When the results is `?Just`: the event was handled by this editor
	, onRefresh :: (?r) EditState *VSt ->
		*(*MaybeErrorString *(UIChange, EditState, ?w), *VSt) //...
		//* React to a new model value
		//* A ?None value can be given to clear the editor
	, writeValue :: !EditState -> MaybeErrorString w //...
		//* Compute the editor's write value from the editor state
	}

derive binumap Editor

/*
*	Definition of a leaf editor using a typed state and edit event.
*	This is an auxiliary type to define an `Editor` with an untyped state and edit events.
*
*   @var edit The type of the edit events
*   @var st   The type of the typed internal state the editor
*   @var r    Read type (see {{Editor}})
*   @var w    Write type (see {{Editor}})
*/
:: LeafEditor edit st r w =
	{ onReset :: !UIAttributes (?r) -> *(*VSt ->
		*(*MaybeErrorString *(UI, st, ?w), *VSt)) //* Generating the initial UI
	, onEdit :: !edit st -> *(*VSt ->
		*(*MaybeErrorString *(UIChange, st, ?w), *VSt)) //* React to edit events
	, onRefresh :: (?r) st -> *(*VSt ->
		*(*MaybeErrorString *(UIChange, st, ?w), *VSt)) //* React to a new model value
	, writeValue :: !st -> MaybeErrorString w //...
		//* Compute the editor's write value from the editor state
	}

leafEditorToEditor :: !(LeafEditor edit st r w) -> Editor r w | TC st & JSONDecode{|*|} edit

//Version without overloading, for use in generic case
leafEditorToEditor_ ::
	!(st -> ?Dynamic) !((?Dynamic) -> (? st)) !(LeafEditor edit st r w)
	-> Editor r w | JSONDecode{|*|} edit

/*
*	Definition of a compound editor using an additional typed state, next to the children's states.
*	This is an auxiliary type to define an `Editor` with an untyped state.
*	The function work on the typed additional state and the untyped children's states.
*
*   @var st   The type of the typed internal state the compound editor
*   @var r    Read type (see {{Editor}})
*   @var w    Write type (see {{Editor}})
*/
:: CompoundEditor st r w =
	{ onReset :: !UIAttributes (?r) *VSt ->
		*(MaybeErrorString (!UI, !st, ![EditState], !?w), *VSt) //* Generating the initial UI
	, onEdit :: (!EditorId, !JSONNode) st [EditState] *VSt ->
		*(MaybeErrorString (?(!UIChange, !st, ![EditState], !?w)), *VSt) //* React to edit events
	, onRefresh :: (?r) st [EditState] *VSt ->
		*(MaybeErrorString (!UIChange, !st, ![EditState], !?w), *VSt) //* React to a new model value
	, writeValue :: !st [EditState] -> MaybeErrorString w //...
		//* Compute the editor's write value from the editor state
	}

compoundEditorToEditor :: !(CompoundEditor st r w) -> Editor r w | TC st

/*
*	Definition of an editor modifier using an additional typed state, next to the child state.
*	Modifiers without additional state can be directly defined in terms of the `Editor` type.
*	This is an auxiliary type to define an `Editor` with an untyped state.
*	The function work on the typed additional state and the untyped child state.
*
*   @var st   The type of the typed additional state of the wrapped editor
*   @var r    Read type (see {{Editor}})
*   @var w    Write type (see {{Editor}})
*/
:: EditorModifierWithState st r w =
	{ onReset :: !UIAttributes (?r) *VSt ->
		*(MaybeErrorString (!UI, !st, !EditState, !?w), *VSt) //* Generating the initial UI
	, onEdit :: (!EditorId, !JSONNode) st EditState *VSt ->
		*(MaybeErrorString (?(!UIChange, !st, !EditState, !?w)), *VSt) //* React to edit events
	, onRefresh :: (?r) st EditState *VSt ->
		*(MaybeErrorString (!UIChange, !st, !EditState, !?w), *VSt) //* React to a new model value
	, writeValue :: !st EditState -> MaybeErrorString w //...
		//* Compute the editor's write value from the editor state
	}

editorModifierWithStateToEditor :: !(EditorModifierWithState st r w) -> Editor r w | TC st

//* Editor id's are unique numbers to link edit events to the corresponding editor
:: EditorId (=: EditorId Int)

instance zero EditorId
instance == EditorId :: !EditorId !EditorId -> Bool :== code { eqI }
instance toString EditorId :: !EditorId -> {#Char} :== code { .d 0 1 i ; jsr ItoAC ; .o 1 0 }
instance fromString EditorId

/**
* Common write type for editors
* Interaction tasks typically expect an editor of type `Editor a (EditorReport a)`.
*
* When composing editors the intermediate compositions can have different types.
* For example, a write type of `(EditorReport a,EditorReport b)` is common when two editors are composed.
* This intermediate type can be easily mapped back to `EditorReport (a,b)` using the utility function `editorReportTuple2`,
* or to just the result of one of the parts by mapping `fst` and `snd` over it.
*/
:: EditorReport a
	= EmptyEditor //* The editor is 'empty' e.g. it contains no data.
	| ValidEditor !a //* The editor is in a valid state
	| InvalidEditor ![String] //* The editor is in a (temporary) invalid state. The list of strings enumerate the problem(s)

instance Functor EditorReport where fmap :: (a -> b) !(EditorReport a) -> EditorReport b
instance pure EditorReport
instance <*> EditorReport
instance Monad EditorReport
derive gPrint EditorReport

//* Lifts a non-valid `EditorReport a` to a `EditorReport b` of another type.
liftedNonValidEditorReport :: !(EditorReport a) -> EditorReport b

//* Convert Editor report to maybe by representing both EmptyEditor and InvalidEditor as ?None
editorReportToMaybe :: !(EditorReport a) -> ?a

//* Convert Editor report to a maybeError by representing EmptyEditor as Error []
editorReportToMaybeError :: !(EditorReport a) -> MaybeError [String] a

//* Convert a MaybeError [String] to an EditorReport
maybeErrorToEditorReport :: !(MaybeError [String] a) -> EditorReport a

/**
* Combine a list of EditorReport values into a single EditorReport
* - If all items are ValidEditor, the combination is a ValidEditor
* - If all items are EmptyEditor, the combination is an EmptyEditor
* - If some items are EmptyEditor or InvalidEditor, the combination is an InvalidEditor
*   Missing items are reported as individual errors.
*/
editorReportList :: ![EditorReport a] -> EditorReport [a]

/**
* Combine a tuple of EditorReport values into a single EditorReport
* Same rules as in `editorReportList` apply.
*/
editorReportTuple2 :: !(!EditorReport a, !EditorReport b) -> EditorReport (!a,!b)
/**
* Combine a 3-tuple of EditorReport values into a single EditorReport
* Same rules as in `editorReportList` apply.
*/
editorReportTuple3 :: !(!EditorReport a, !EditorReport b, !EditorReport c) -> EditorReport (!a,!b,!c)
/**
* Combine a 4-tuple of EditorReport values into a single EditorReport
* Same rules as in `editorReportList` apply.
*/
editorReportTuple4 ::
	!(!EditorReport a, !EditorReport b, !EditorReport c, !EditorReport d) -> EditorReport (!a,!b,!c,!d)
/**
* Combine a 5-tuple of EditorReport values into a single EditorReport
* Same rules as in `editorReportList` apply.
*/
editorReportTuple5 ::
	!(!EditorReport a, !EditorReport b, !EditorReport c, !EditorReport d, !EditorReport e)
	-> EditorReport (!a,!b,!c,!d,!e)

/** Edit masks contain information about a value as it is being edited in an interactive task.
*   During editing, values can be in an inconsistent, or even untypable state
*/  
:: EditState
	= LeafState      !LeafState               //* Edit state of single fields/controls
	| CompoundState  !(?Dynamic) ![EditState] //* Compound structure of multiple editors with additional extra state
	| AnnotatedState !(?Dynamic) !EditState   //* Edit state annotated with additional information, used for modifiers

:: LeafState =
	{ editorId :: !?EditorId
	, touched  :: !Bool
	, state    :: !?Dynamic //* Usually contains the value
	}

:: *VSt =
	{ taskId            :: !String //* The id of the task the visualisation belongs to
	, nextEditorId      :: !EditorId //* Source of unique editor identifiers
	, optional          :: !Bool //* Create optional form fields
	, selectedConsIndex :: !Int //* Index of the selected constructor in an OBJECT
	, pathInEditMode    :: [Bool] //* Path of LEFT/RIGHT choices used when UI is generated in edit mode
	, abcInterpreterEnv :: !PrelinkedInterpretationEnvironment //* Used to serialize expressions for the client
	, engineOptions     :: !EngineOptions //* The engine options of the iTasks server
	}

withVSt :: !TaskId !EditorId !.(*VSt -> (a, *VSt)) !*IWorld -> (!a, !EditorId, !*IWorld)
nextEditorId :: !*VSt -> *(!EditorId,!*VSt)

derive JSONEncode EditState, LeafState
derive JSONDecode EditState, LeafState

isTouched :: !EditState -> Bool
isCompound :: !EditState -> Bool

:: FrontendEngineOptions =
	{ serverDirectory :: !String
	}

/**
 * Add client-side initialization to the generation of an initial UI. See the
 * modules ABC.Interpreter.JavaScript and ABC.Interpreter.JavaScript.Monad for
 * functions that can be used in the client-side function.
 *
 * All client-side functions are currently shared in a single instance of an
 * ABC interpreter in WebAssembly. See itasks-core.js for the implementation
 * and the default settings.
 *
 * When using a client-side function that requires more memory than the
 * default, you can create a custom index.html that sets a large heap/stack
 * size before loading itasks-core.js:
 *
 * ```html
 * <script>
 *     var itasks = {
 *         settings: {
 *             heap_size: 64 << 20, // 64MB
 *             stack_size: 1 << 20, // 1MB
 *         }
 *     };
 * </script>
 * ```
 */
withClientSideInit ::
	!(FrontendEngineOptions JSVal *JSWorld -> *JSWorld)
	!(Editor r w) -> Editor r w

//* Like `withClientSideInit`, but for `LeafEditor`.
withClientSideInitOnLeafEditor ::
	!(FrontendEngineOptions JSVal *JSWorld -> *JSWorld)
	!(LeafEditor edit st r w) -> LeafEditor edit st r w

/**
* Experimental modifier to move all event processing of an editor
* to the client.
* ONLY AS DEMONSTRATION, DO NOT USE YET
* Currently when using this modifier, nothing is communicated back to the server anymore
*/
withClientSideEventHandling :: (Editor r w) -> (Editor r w)
