packages feed

spectacle-1.0.0: src/Language/Spectacle/Interaction/Paths.hs

{-# LANGUAGE RecordWildCards #-}

-- |
--
-- @since 1.0.0
module Language.Spectacle.Interaction.Paths
  ( -- * Construction
    toPointSet,
    takeMinRow,
  )
where

import qualified Data.Set as Set
import Data.Set.Internal (Set (Bin, Tip))
import Lens.Micro ((.~), (?~), (^.))

import Control.Monad.State (execState, gets, modify)
import Data.Foldable (traverse_)
import Data.Function ((&))
import Data.Functor.Tree (Tree, pattern (:-))
import Data.Type.Rec (HasDict)
import Data.World (World, fingerprint)
import Language.Spectacle.Interaction.Point (Point, column, extent, fromWorld, parent, row)

-- ---------------------------------------------------------------------------------------------------------------------

toPointSet :: HasDict Show ctx => Tree (World ctx) -> Set Point
toPointSet = flip execState Set.empty . start
  where
    start (w :- ws) = do
      c <- gets (columnsOn 0)
      let point =
            fromWorld w
              & column .~ c
              & extent .~ length ws

      modify (Set.insert point)
      traverse_ (go 1 (w ^. fingerprint)) ws

    go row0 par (w :- ws) = do
      col <- gets (columnsOn row0)
      let point =
            fromWorld w
              & parent ?~ par
              & extent .~ length ws
              & column .~ col
              & row .~ row0
      modify (Set.insert point)
      traverse_ (go (1 + row0) (w ^. fingerprint)) ws

-- | Like 'splitRow', but always splits on the least row in the set.
--
-- @since 1.0.0
takeMinRow :: Set Point -> (Set Point, Set Point)
takeMinRow ps =
  case Set.minView ps of
    Nothing -> (Set.empty, Set.empty)
    Just (p, _) -> splitRow (p ^. row) ps

-- | @'splitRow' i ps@ returns a subset of @ps@ containing all elements located on row @i@ and a new set stripped of
-- this row.
--
-- @since 1.0.0
splitRow :: Int -> Set Point -> (Set Point, Set Point)
splitRow i = seek Set.empty
  where
    seek acc Tip = (acc, Tip)
    seek acc (Bin _ !p ls rs) =
      case compare (p ^. row) i of
        LT ->
          let (acc', rs') = seek acc rs
           in (acc', Set.union (Set.insert p rs') ls)
        GT ->
          let (acc', ls') = seek acc ls
           in (acc', Set.union (Set.insert p ls') rs)
        EQ ->
          let (acc0, ls') = seek acc ls
              (acc1, rs') = seek acc0 rs
           in (Set.insert p acc1, Set.union ls' rs')

columnsOn :: Int -> Set Point -> Int
columnsOn i = Set.size . Set.filter (\pt -> pt ^. row == i)