implementation module EdAction

import StdInt, StdArray, StdTuple, StdBool, StdChar, StdList, StdMisc
import StdIOCommon, StdPSt, StdWindow
import EdVisualCursor, EdVisualText, EdMovement, StrictList, EdMonad
import EdActionType, EdSelection

import EdCommon

performAction :: Action -> EditMonad (PSt PLocState) nothing
performAction action =
        // things to do before the action is applied
        undoStuff action									>>>
		vHideCursor											>>>
        onlyIf (needsCenterCursor action) 
                vCenterCursor								>>>
        getSelection										>>>= \selection ->
        onlyIf (needsResetVirtualX action selection)
                ( setVirtualX 0 )							>>>
        onlyIf (needsRemoveSelection action)
                removeSelectionIfNecessary					>>>= \selectionRemoved ->
        // apply the action
        applyAction selectionRemoved action					>>>
        vShowCursor

applyAction :: Bool Action -> EditMonad (PSt PLocState) nothing
applyAction selectionRemoved action =
        case action of

          // Move actions are performed in the following way. If there is a selection,
          // the cursor moves to the start or end of the selection. Otherwise, the
          // new position is computed by "positionAfterMove". After moving the cursor,
          // the selection is emptied and hidden.
         
          Move move ->
                getSelection                                  >>>= \selection ->
                IF (selection.start == selection.end)
                THEN
                  ( positionAfterMove move selection.start          >>>= \newPos ->
	                vChangeSelectionTo {start=newPos,end=newPos}	>>>
	                mChangeSelectionTo {start=newPos,end=newPos}	>>>
					IF (move == PageUp || move == PageDown)
					THEN
						(vMoveCursor move)
					ELSE
						vScrollToCursor
				  )
                ELSE 
                  (
                      ( if (isMember move [CharLeft,WordLeft,PageUp,LineUp,StartOfLine])
                                    (result (orderSelection selection).start)
                                    (case move of
                                    	StartOfText	-> positionAfterMove move selection.start
                                    	EndOfText	-> positionAfterMove move selection.start
                                    	_			-> result (orderSelection selection).end
                                    )
                      )                                             >>>= \newPos ->
	                vChangeSelectionTo {start=newPos,end=newPos}	>>>
	                mChangeSelectionTo {start=newPos,end=newPos}	>>>
					vScrollToCursor
                  )

          // The insert action is mainly done by "vInsertText". It inserts the
          // text into the internal representation and then redraws as much of
          // the screen as is necessary. After inserting the text, the cursor
          // is position behind that text.
          
          Insert fragment ->
            getSelection                                  >>>= \selection ->
            let newPos = positionAfterText selection.start fragment
            in
            vInsertText selection.start fragment            >>>
            vChangeSelectionTo {start=newPos,end=newPos}    >>> 
            mChangeSelectionTo {start=newPos,end=newPos}    >>>
			vScrollToCursor									>>>
            setNeedSave True

          // Scrolling has no effect on the cursor position. It just 
          // changes which part of the whole view domain is visible.
          
          Scroll move ->
                getFontInfo                                  >>>= \{lineHeight} ->
                getText                                      >>>= \text ->
                getWindowId                                  >>>= \windowId ->
                accEnv (accPIO (getWindowViewFrame windowId))>>>= \frame ->
                let
                	linesInFrame        = (frame.corner2.y - frame.corner1.y) / lineHeight
                    pagePixels          = (linesInFrame - 1) * lineHeight 
                    topPixel			= 0
                    botPixel			= textLength text * lineHeight
                    newTopPixel         = case move of
                                            PageUp          -> max topPixel (frame.corner1.y - pagePixels)
                                            PageDown        -> min botPixel (frame.corner1.y + pagePixels)
                                            StartOfText     -> topPixel
                                            EndOfText       -> botPixel - pagePixels
                                            _				-> frame.corner1.y	// if unknown -> ignore
 
                in
                appEnv (appPIO (moveWindowViewFrame windowId {vx=0, vy=newTopPixel-frame.corner1.y}))
 
          // A select action changes the current selection if there is any and
          // otherwise starts a new one.
          
          Select move ->
			getSelection                                            >>>= \selection ->
			positionAfterMove move selection.end                    >>>= \newPos ->
			let selectionStart = selection.start
			in
			vChangeSelectionTo {start=selectionStart,end=newPos}	>>>
			mChangeSelectionTo {start=selectionStart,end=newPos}	>>>
			vScrollToCursor									

          // A remove action has to remove the selection if there is any. This
          // has already been done by the 'preprocessing' in "performAction".
          // If that preprocessing has removed the selection, nothing has to
          // be done here. Otherwise, the position after the movement is computed
          // and every character from the original position to the new one is
          // removed.
          
          Remove move ->
                setNeedSave True                                >>>
            IF selectionRemoved
            THEN skip
            ELSE
              (
                getSelection									>>>= \{start} ->
                positionAfterMove move start                    >>>= \newPos ->
                setSelection {start=start,end=newPos}           >>>
                vRemoveSelection								>>>
				mRemoveSelection
              )													>>>
			vScrollToCursor									
              

          _ ->
                abort "applyAction (EdAction.icl): unknown action"

where
        positionAfterText :: !Position !TextFragment -> Position
        positionAfterText position SNil = position
        positionAfterText {col, row} (SCons string SNil)
          = {col=col+size string, row=row}
        positionAfterText {row} strings
          = {col=size (slLast strings), row=row+slLength strings-1}


// The "needs..." functions specify whether certain actions
// need certain preprocessing or postprocessing.
 
needsCenterCursor :: Action -> Bool
needsCenterCursor (Scroll _)    = False
needsCenterCursor _             = True

needsRemoveSelection :: Action -> Bool
needsRemoveSelection (Remove _) = True
needsRemoveSelection (Insert _) = True
needsRemoveSelection _                  = False

needsResetVirtualX :: Action Selection -> Bool
needsResetVirtualX (Scroll _) _ = False
needsResetVirtualX (Move move) selection
  | isVerticalMove move
    = not (isEmptySelection selection)
    = True
needsResetVirtualX (Select move) _
  = not (isVerticalMove move)
needsResetVirtualX _ _ = True

// removeSelectionIfNecessary removes the selection if there
// is any. It also results a boolean to indicate whether removal
// was necessary.

removeSelectionIfNecessary :: EditMonad (PSt PLocState) Bool
removeSelectionIfNecessary
  = getSelection                                                >>>= \selection ->
        IF (not (isEmptySelection selection))
        THEN
          (
                vRemoveSelection                                >>>
 				mRemoveSelection								>>>
                result True
          )
        ELSE
          ( result False )

undoStuff :: !Action -> EditMonad (PSt .l) nothing
undoStuff (Insert _) =
        getUndoInfo                                     >>>= \undoinfo ->
        case undoinfo.uninfo of
        (InsertInfo True _) -> skip
        _ ->
                getState                                >>>= \state ->
                setUndoInfo {state=Undo,action=" Typing",uninfo=(InsertInfo True state)}
undoStuff (Remove _) =
        getUndoInfo                                     >>>= \undoinfo ->
        case undoinfo.uninfo of
        (RemoveInfo True _) -> skip
        _ ->
                getState                                >>>= \state ->
                setUndoInfo {state=Undo,action=" Deletion",uninfo=(RemoveInfo True state)}
undoStuff _ =
        getUndoInfo                                     >>>= \undoinfo ->
        case undoinfo.uninfo of
        (InsertInfo True state) -> setUndoInfo {undoinfo & uninfo=(InsertInfo False state)}
        (RemoveInfo True state) -> setUndoInfo {undoinfo & uninfo=(RemoveInfo False state)}
        _ -> skip

undoAction :: EditMonad (PSt .l) nothing
undoAction =
        getUndoInfo                                     >>>= \undoinfo ->
        getState                                        >>>= \fin ->
        case undoinfo.uninfo of
        (InsertInfo _ ini) -> setUndoInfo {undoinfo & uninfo=(UndoneInfo ini fin)}
        (RemoveInfo _ ini) -> setUndoInfo {undoinfo & uninfo=(UndoneInfo ini fin)}
        _ -> skip
