packages feed

tinytools-0.1.0.0: src/Potato/Flow/Deprecated/Workspace.hs

{-# LANGUAGE RecordWildCards #-}

module Potato.Flow.Deprecated.Workspace (
  PFWorkspace(..)
  , emptyWorkspace
  , emptyActionStack
  , loadPFStateIntoWorkspace
  , undoWorkspace
  , redoWorkspace
  , undoPermanentWorkspace
  , doCmdWorkspace
  , pfc_addElt_to_newElts
  , pfc_addFolder_to_newElts
  , pfc_removeElt_to_deleteElts
  , pfc_paste_to_newElts
  , WSEvent(..)
  , updatePFWorkspace
) where

import           Relude

import           Potato.Flow.Cmd
import           Potato.Flow.Deprecated.Layers
import           Potato.Flow.Math
import           Potato.Flow.SElts
import           Potato.Flow.Deprecated.State
import           Potato.Flow.Types

import           Control.Exception  (assert)
import           Data.Dependent.Sum (DSum ((:=>)), (==>))
import qualified Data.IntMap.Strict as IM
import qualified Data.Sequence      as Seq

-- TODO move this into a diff file
data ActionStack = ActionStack {
  doStack     :: [PFCmd] -- maybe just do something lke [PFCmd, Maybe PFState] here for state based undo
  , undoStack :: [PFCmd]
} deriving (Show, Eq, Generic)

instance NFData ActionStack

emptyActionStack :: ActionStack
emptyActionStack = ActionStack [] []

data PFWorkspace = PFWorkspace {
  _pFWorkspace_pFState       :: PFState
  , _pFWorkspace_lastChanges :: SEltLabelChanges
  , _pFWorkspace_actionStack :: ActionStack
} deriving (Show, Eq, Generic)

instance NFData PFWorkspace

loadPFStateIntoWorkspace :: PFState -> PFWorkspace -> PFWorkspace
loadPFStateIntoWorkspace pfs ws = r where
  removeOld = fmap (const Nothing) (_pFState_directory . _pFWorkspace_pFState $ ws)
  addNew = fmap Just (_pFState_directory pfs)
  changes = IM.union addNew removeOld
  r = PFWorkspace pfs changes emptyActionStack

emptyWorkspace :: PFWorkspace
emptyWorkspace = PFWorkspace emptyPFState IM.empty emptyActionStack

undoWorkspace :: PFWorkspace -> PFWorkspace
undoWorkspace pfw =  r where
  ActionStack {..} = _pFWorkspace_actionStack pfw
  r = case doStack of
    c : cs -> uncurry PFWorkspace (undoCmdState c (_pFWorkspace_pFState pfw)) (ActionStack cs (c:undoStack))
    _ -> pfw

redoWorkspace :: PFWorkspace -> PFWorkspace
redoWorkspace pfw = r where
  ActionStack {..} = _pFWorkspace_actionStack pfw
  r = case undoStack of
    c : cs -> uncurry PFWorkspace (doCmdState c (_pFWorkspace_pFState pfw)) (ActionStack (c:doStack) cs)
    _ -> pfw

undoPermanentWorkspace :: PFWorkspace -> PFWorkspace
undoPermanentWorkspace pfw =  r where
  ActionStack {..} = _pFWorkspace_actionStack pfw
  r = case doStack of
    c : cs -> uncurry PFWorkspace (undoCmdState c (_pFWorkspace_pFState pfw)) (ActionStack cs undoStack)
    _ -> pfw

doCmdWorkspace :: PFCmd -> PFWorkspace -> PFWorkspace
-- deepseq here to force evaluation of workspace and prevent leaks
doCmdWorkspace cmd pfw = force r where
  newState = doCmdState cmd (_pFWorkspace_pFState pfw)
  ActionStack {..} = (_pFWorkspace_actionStack pfw)
  newStack = ActionStack (cmd:doStack) []
  --newMaxId = pFState_maxID _pFWorkspace_pFState
  r = uncurry PFWorkspace newState newStack

doCmdState :: PFCmd -> PFState -> (PFState, SEltLabelChanges)
doCmdState cmd s = assert (pFState_isValid newState) (newState, changes) where
  (newState, changes) = case cmd of
    (PFCNewElts :=> Identity x)      ->  do_newElts x s
    (PFCDeleteElts :=> Identity x)   ->  do_deleteElts x s
    (PFCManipulate :=> Identity x)   ->  do_manipulate x s
    (PFCMove :=> Identity x)         -> do_move x s
    (PFCResizeCanvas :=> Identity x) -> (do_resizeCanvas x s, IM.empty)

undoCmdState :: PFCmd -> PFState -> (PFState, SEltLabelChanges)
undoCmdState cmd s = assert (pFState_isValid newState) (newState, changes) where
  (newState, changes) =  case cmd of
    (PFCNewElts :=> Identity x)      ->  undo_newElts x s
    (PFCDeleteElts :=> Identity x)   ->  undo_deleteElts x s
    (PFCManipulate :=> Identity x)   ->  undo_manipulate x s
    (PFCMove :=> Identity x)         -> undo_move x s
    (PFCResizeCanvas :=> Identity x) -> (undo_resizeCanvas x s, IM.empty)

------ helpers for converting events to cmds
-- TODO move these to a different file prob
pfc_addElt_to_newElts :: PFState -> (LayerPos, SEltLabel) -> PFCmd
pfc_addElt_to_newElts pfs (lp,seltl) = r where
  rid = pFState_maxID pfs + 1
  r = PFCNewElts ==> [(rid,lp,seltl)]

pfc_addFolder_to_newElts :: PFState -> (LayerPos, Text) -> PFCmd
pfc_addFolder_to_newElts pfs (lp, name) = r where
  ridStart = pFState_maxID pfs + 1
  ridEnd = ridStart + 1
  seltlStart = SEltLabel name SEltFolderStart
  seltlEnd = SEltLabel (name <> " (end)") SEltFolderEnd
  r = PFCNewElts ==> [(ridStart, lp, seltlStart), (ridEnd, lp+1, seltlEnd)]

debugPrintLayerPoss :: (IsString a) => PFState -> [LayerPos] -> a
debugPrintLayerPoss PFState {..} lps = fromString msg where
  rids = map (Seq.index _pFState_layers) lps
  seltls = map ((IM.!) _pFState_directory) rids
  msg = show $ (zip3 rids lps (map _sEltLabel_sElt seltls))

-- TODO consider including folder end to selecetion if not included
-- or at least assert to ensure it's correct
pfc_removeElt_to_deleteElts :: PFState -> [LayerPos] -> PFCmd
--pfc_removeElt_to_deleteElts pfs@PFState {..} lps = if length lps > 1 then trace (debugPrintLayerPoss pfs lps) r else r where
pfc_removeElt_to_deleteElts PFState {..} lps = r where
  rids = map (Seq.index _pFState_layers) lps
  seltls = map ((IM.!) _pFState_directory) rids
  r = PFCDeleteElts ==> (zip3 rids lps seltls)

-- TODO DELETE
pfc_paste_to_newElts :: PFState -> ([SEltLabel], LayerPos) -> PFCmd
pfc_paste_to_newElts pfs (seltls, lp) = r where
  rid = pFState_maxID pfs + 1
  r = PFCNewElts ==> zip3 [rid..] [lp..] seltls

pfc_addRelative_to_newElts :: PFState -> (LayerPos, SEltTree) -> PFCmd
pfc_addRelative_to_newElts pfs (lp, stree) = assert validScope $ r where
  validScope = selectionHasScopingProperty scopeFn (Seq.fromList stree) [0..length stree - 1]
  scopeFn (_,seltl) = case seltl of
    (SEltLabel _ SEltFolderStart) -> Just True
    (SEltLabel _ SEltFolderEnd)   -> Just False
    _                             -> Nothing
  -- TODO reposition/offset (could just offset by 1? or maybe need to add new arg)
  -- TODO reindex SEltTree maintaing connections
  rid = pFState_maxID pfs + 1
  r = PFCNewElts ==> zip3 [rid..] [lp..] (fmap snd stree)

--pfc_duplicate_to_duplicate :: PFState -> [LayerPos] -> PFCmd
--pfc_duplicate_to_duplicate pfs lps = r where
--  rids = map (Seq.index _pFState_layers) lps
--  r = PFCFDuplicate ==> rids

------ update functions via commands
data WSEvent =
  -- CHANGE TODO FIGURE IT OUT
  --WSEAddElt (Bool, OwlSpot, OwlItem)
  -- | WSEAddRelative (OwlSpot, Seq OwlItem)
  -- | WSEAddFolder (OwlSpot, Text)
  -- | WSERemoveElt [REltId] -- removed kiddos get adopted by grandparents or w/e?
  -- | WSEMoveElt (OwlSpot, [REltId]) -- also moves kiddos?
  -- | WSEDuplicate [REltId] -- kiddos get duplicated??


  WSEAddElt (Bool, (LayerPos, SEltLabel))
  | WSEAddRelative (LayerPos, SEltTree)
  | WSEAddFolder (LayerPos, Text)
  | WSERemoveElt [LayerPos]
  | WSEMoveElt ([LayerPos], LayerPos)
  -- | WSEDuplicate [LayerPos]
  | WSEManipulate (Bool, ControllersWithId)
  | WSEResizeCanvas DeltaLBox
  | WSEUndo
  | WSERedo
  | WSELoad SPotatoFlow
  deriving (Show, Eq)

debugPrintBeforeAfterState :: (IsString a) => PFState -> PFState -> a
debugPrintBeforeAfterState stateBefore stateAfter = fromString $ "BEFORE: " <> debugPrintPFState stateBefore <> "\nAFTER: " <> debugPrintPFState stateAfter

doCmdPFWorkspaceUndoPermanentFirst :: (PFState -> PFCmd) -> PFWorkspace -> PFWorkspace
doCmdPFWorkspaceUndoPermanentFirst cmdFn ws = r where
  -- undoPermanent is actually not necessary as the next action clears the redo stack anyways
  undoedws = undoPermanentWorkspace ws
  undoedpfs = _pFWorkspace_pFState undoedws
  cmd = cmdFn undoedpfs
  r = doCmdWorkspace cmd undoedws

updatePFWorkspace :: WSEvent -> PFWorkspace -> PFWorkspace
updatePFWorkspace evt ws = let
  lastState = _pFWorkspace_pFState ws
  r = case evt of
    WSEAddElt (undo, x) -> if undo
      then doCmdPFWorkspaceUndoPermanentFirst (\pfs -> pfc_addElt_to_newElts pfs x) ws
      else doCmdWorkspace (pfc_addElt_to_newElts lastState x) ws
    WSEAddRelative x -> doCmdWorkspace (pfc_addRelative_to_newElts lastState x) ws
    WSEAddFolder x -> doCmdWorkspace (pfc_addFolder_to_newElts lastState x) ws
    WSERemoveElt x -> doCmdWorkspace (pfc_removeElt_to_deleteElts lastState x) ws
    WSEManipulate (undo, x) -> if undo
      then doCmdPFWorkspaceUndoPermanentFirst (const (PFCManipulate ==> x)) ws
      else doCmdWorkspace (PFCManipulate ==> x) ws
    -- TODO add children to selection before moving
    WSEMoveElt x -> doCmdWorkspace (PFCMove ==> x) ws
    WSEResizeCanvas x -> doCmdWorkspace (PFCResizeCanvas ==> x) ws
    WSEUndo -> undoWorkspace ws
    WSERedo -> redoWorkspace ws
    WSELoad x -> loadPFStateIntoWorkspace (sPotatoFlow_to_pFState x) ws
  afterState = _pFWorkspace_pFState r
  isValidAfter = pFState_isValid afterState
  in
    if isValidAfter then r else
      error ("INVALID " <> show evt <> "\n" <> debugPrintBeforeAfterState lastState afterState)