module Sifflet.UI.Frame
    (
     CanvFrame(..), FrameType(..)
    , argIoletCounter
    , atLeastSizeFrame
    , cfEvalReady
    , cfPointInHeader
    , cfPointInFooter
    , cfRoot
    , frameNewWithLayout
    , frameBodyBox
    , frameNodeAt
    , frameOffset
    , levelOrder
    , nodeCompoundFunction
    , pointIolet
    , resizeFrame
    , translateFrame
    , grTranslateFrameNodes
    )
where
import Data.Function
import Data.List
import Data.Graph.Inductive as G
import Sifflet.Data.Functoid
import Sifflet.Data.Geometry
import Sifflet.Data.Tree
import Sifflet.Data.TreeLayout
import Sifflet.Data.WGraph
import Sifflet.Language.Expr
import Sifflet.Util
data CanvFrame = CanvFrame {
      cfHeader :: TextBox       
    , cfFooter :: TextBox       
    , cfVarNames :: [String]    
    , cfParent :: Maybe G.Node  
    , cfFrameNode :: G.Node     
                                
    , cfEnv :: Env              
    , cfBox :: BBox 
    , cfLevel :: Double         
    , cfFunctoid :: Functoid    
    , frameType :: FrameType    
    }
                 deriving (Show)
                 
instance Eq CanvFrame where (==) = (==) `on` cfFrameNode
data FrameType = EditFrame | CallFrame deriving (Eq, Read, Show)
levelOrder :: CanvFrame -> CanvFrame -> Ordering
levelOrder f1 f2 = compare (cfLevel f1) (cfLevel f2) 
cfRoot :: CanvFrame -> G.Node
cfRoot frame = 
    case frameType frame of
      EditFrame -> error "cfRoot: an edit frame has no root"
      CallFrame -> succ (cfFrameNode frame)
cfEvalReady :: CanvFrame -> Bool
cfEvalReady frame = 
    (frameType frame == CallFrame) && (cfParent frame == Nothing)
cfPointInHeader :: CanvFrame -> Double -> Double -> Bool
cfPointInHeader frame x y = 
    pointInBB (Position x y) (tbBoxBB (cfHeader frame))
cfPointInFooter :: CanvFrame -> Double -> Double -> Bool
cfPointInFooter frame x y = 
    pointInBB (Position x y) (tbBoxBB (cfFooter frame))
frameBodyBox :: CanvFrame -> BBox
frameBodyBox frame =
    
    let hbb = tbBoxBB (cfHeader frame)
        fbb = tbBoxBB (cfFooter frame)
        top = bbBottom hbb
        bottom = bbTop fbb
    
    in BBox (bbLeft hbb) top (bbWidth hbb) (bottom  top)
editFrameNodes :: CanvFrame -> [G.Node]
editFrameNodes frame =
    case frameType frame of
      EditFrame -> fpNodes (cfFunctoid frame)
      CallFrame -> error "editFrameNodes: not an EditFrame"
frameNodeAt :: CanvFrame -> WGraph -> Position -> Maybe G.Node
frameNodeAt frame graph point = 
    case frameType frame of
      CallFrame -> callFrameNodeAt frame graph point
      EditFrame -> editFrameNodeAt frame graph point
editFrameNodeAt  :: CanvFrame -> WGraph -> Position -> Maybe G.Node
editFrameNodeAt _frame _graph _point = Nothing 
  
  
callFrameNodeAt :: CanvFrame -> WGraph -> Position -> Maybe G.Node
callFrameNodeAt frame graph point = 
    let search :: [G.Node] -> Maybe G.Node
        search [] = Nothing
        search (r:rs) =
            case lab graph r of
              Just (WFrame _) -> search rs
              Just (WSimple layoutNode) ->
                  let LayoutNode rootGNode treeBB = layoutNode
                  in
                    if pointInBB point treeBB 
                    
                    then if pointInBB point (gnodeNodeBB rootGNode) 
                         
                         then Just r
                         else search (suc graph r) 
                    else search rs                 
              Nothing -> 
                  errcats ["editFrameNodeAt: search: no label for node",
                           show r]
    
    in search [cfRoot frame]
atLeastSizeFrame :: Size -> CanvFrame -> CanvFrame
atLeastSizeFrame (Size minW minH) frame =
    let BBox _ _ width height = cfBox frame
        dwidth = if minW > width then minW  width else 0
        dheight = if minH > height then minH  height else 0
    in resizeFrame frame dwidth dheight
resizeFrame :: CanvFrame -> Double -> Double -> CanvFrame
resizeFrame frame dw dh = 
    let BBox x y bwidth height = frameBodyBox frame
        
        bwidth' = max 0 (bwidth + dw)
        height' = max 0 (height + dh)
        bodyBB' = BBox x y bwidth' height'
        header' = alignHeader (cfHeader frame) bodyBB'
        footer' = alignFooter (cfFooter frame) bodyBB'
        frameBox' = bbMergeList [tbBoxBB header', tbBoxBB footer', bodyBB']
    in frame {cfHeader = header', cfFooter = footer', cfBox = frameBox'}
translateFrame :: CanvFrame -> Double -> Double -> CanvFrame
translateFrame frame dx dy =
    frame {cfHeader = translate dx dy (cfHeader frame),
           cfFooter = translate dx dy (cfFooter frame),
           cfBox = translate dx dy (cfBox frame)}
frameOffset :: Style -> CanvFrame -> Position
frameOffset style oldFrame = 
    let bb = cfBox oldFrame
    in Position (bbRight bb + styleFramePad style) (bbTop bb  40)
nodeCompoundFunction :: WGraph -> CanvFrame -> Node -> Maybe Function
nodeCompoundFunction graph frame node = 
    case lab graph node of
      Nothing -> error "nodeCompoundFunction: no label for node"
      Just (WFrame _) -> error "nodeCompoundFunction: node has a WFrame label"
      Just (WSimple layoutNode) ->
          case gnodeValue (nodeGNode layoutNode) of
            ENode (NSymbol (Symbol "if")) _mvalue -> Nothing 
            ENode (NSymbol (Symbol symbolName)) _mvalue ->
                case envLookup (cfEnv frame) symbolName of
                  Nothing -> Nothing 
                  Just (VFun func@(Function _ _ _ (Compound _ _))) -> Just func
                  Just _ -> Nothing 
            _ -> Nothing 
grTranslateFrameNodes :: WGraph -> CanvFrame -> Double -> Double -> WGraph
grTranslateFrameNodes wgraph frame dx dy =
    case frameType frame of
      CallFrame -> translateTree dx dy wgraph (cfRoot frame)
      EditFrame -> translateNodes dx dy wgraph (editFrameNodes frame)
      
pointIolet :: Position -> Int -> [Iolet] -> Maybe Int
pointIolet point n iolets =
    
    case iolets of 
      [] -> Nothing
      (p:ps) ->
          if pointInIolet point p 
          then Just n
          else pointIolet point (n + 1) ps
argIoletCounter :: [String] -> ExprNode -> (Int, Int)
argIoletCounter labels _exprNode = (length labels, 1)
alignHeader :: TextBox -> BBox -> TextBox
alignHeader header bodybox =
    let headerBB = tbBoxBB header
        y0 = bbBottom headerBB
        y1 = bbTop bodybox
        x0 = bbLeft headerBB
        x1 = bbLeft bodybox
    in translate (x1  x0) (y1  y0) (tbSetWidth header (bbWidth bodybox))
alignFooter :: TextBox -> BBox -> TextBox
alignFooter footer bodybox =
    let footerBB = tbBoxBB footer
        y0 = bbTop footerBB
        y1 = bbBottom bodybox
        x0 = bbLeft footerBB
        x1 = bbLeft bodybox
    in translate (x1  x0) (y1  y0) (tbSetWidth footer (bbWidth bodybox))
frameNewWithLayout :: Style -> Position -> Double
                   -> Functoid -> Maybe [Value] -> FrameType 
                   -> Node -> Env -> Maybe G.Node 
                   -> (CanvFrame, FunctoidLayout) 
frameNewWithLayout style (Position x y) z 
                   functoid mvalues mode frameNode prevEnv mparent = 
  
  
  let headerText = functoidHeader functoid
      vars = functoidArgNames functoid
      footerText = buildFooterText vars mvalues
      env = case mvalues of
              Nothing ->
                  
                  extendEnv [] [] prevEnv 
              Just values ->
                  extendEnv vars values prevEnv
      
      layout0 = flayout style functoid env mvalues
      
      headerTB0 = makeTextBox style headerText 
      footerTB0 = makeTextBox style footerText 
      
      Size lw lh = flayoutSize layout0
      fwidth = maximum [tbWidth headerTB0, lw, tbWidth footerTB0]
      
      
      headerTB1 = translate x y (widen headerTB0 fwidth)
      (dx, dy) = (x  hpad style, tbBottom headerTB1  vpad style)
      layout1 = translate dx dy (flayoutWiden layout0 fwidth)
      footerTB1 = translate x (flayoutBottom layout1) 
                  (widen footerTB0 fwidth)
      frameBox = BBox x y fwidth 
                 (tbHeight headerTB0 + lh + tbHeight footerTB0)
      frame = CanvFrame {cfHeader = headerTB1,
                         cfFooter = footerTB1,
                         cfVarNames = vars,
                         cfParent = mparent,
                         cfFrameNode = frameNode,
                         cfEnv = env, 
                         cfBox = frameBox,
                         cfLevel = z,
                         cfFunctoid = functoid,
                         frameType = mode}
  in (frame, layout1)
buildFooterText :: [String] -> Maybe [Value] -> String
buildFooterText vars mvalues = 
    let items =
            case mvalues of
              Nothing -> vars
              Just [] -> vars
              Just values ->
                  if length vars /= length values
                  then error "buildFooterText: mismatched lists"
                  else [var ++ " = " ++ repr value |
                        (var, value) <- zip vars values]
    in concat (intersperse ", " items)