content: -- Copyright 2015 Evan Laforge
  -- This program is distributed under the terms of the GNU General Public
  -- License 3.0, see COPYING or http://www.gnu.org/licenses/gpl-3.0.txt
  
  -- | The selection type.
  module Ui.Sel (
      Ui.Sel.Num
      , Selection(..)
      , Orientation(..)
      , point, is_point
      , modify_tracks
      , expand_tracks
      , track_range
      , tracknums
      , min, max
      , range
      , event_orientation
      , duration
      , merge
      , union
      , move
  ) where
  import qualified Prelude
  import           Prelude hiding (min, max)
  import qualified Data.Tuple as Tuple
  
  import qualified Util.Num as Num
  import qualified Ui.Types as Types
  
  import           Global
  import           Types
  
  
  -- | Index into the the selection list.
  type Num = Int
  
  data Selection = Selection {
      -- | The position the selection was established at.  Since a selection can
      -- logically go off the edge of a block, this is not necessarily a valid
      -- TrackNum!
      start_track :: !TrackNum
      , start_pos :: !TrackTime
  
      -- | The position the selection is now at.  The tracks are an inclusive
      -- range, the pos are half-open.  This is because these pairs are meant to
      -- be symmetrical, but the c++ layer only supports half-open pos ranges.
      -- I don't think there's much I can do about this.
      --
      -- Unlike 'start_track', this should be a valid TrackNum, because cmds want
      -- to use it as the focused track.
      , cur_track :: !TrackNum
      , cur_pos :: !TrackTime
      , orientation :: !Orientation
      } deriving (Eq, Ord, Show, Read)
  
  instance Pretty Selection where
      pretty (Selection strack spos ctrack cpos orientation) =
          "Selection" <> o <> pretty (strack, spos) <> "--"
              <> pretty (ctrack, cpos)
          where
          o = case orientation of
              None -> "_"
              Positive -> "+"
              Negative -> "-"
  
  -- | None is used for display selections, which don't need arrows on them.
  data Orientation = None | Positive | Negative
      deriving (Eq, Ord, Enum, Bounded, Show, Read)
  
  -- | A point is a selection with no duration.
  point :: TrackNum -> TrackTime -> Orientation -> Selection
  point tracknum pos orientation = Selection
      { start_track = tracknum, start_pos = pos
      , cur_track = tracknum, cur_pos = pos
      , orientation = orientation
      }
  
  is_point :: Selection -> Bool
  is_point sel = start_pos sel == cur_pos sel
  
  modify_tracks :: (TrackNum -> TrackNum) -> Selection -> Selection
  modify_tracks f sel = sel
      { start_track = f (start_track sel)
      , cur_track = f (cur_track sel)
      }
  
  expand_tracks :: TrackNum -> Selection -> Selection
  expand_tracks n sel
      | cur > start = sel { cur_track = cur + n }
      | otherwise = sel { start_track = start + n }
      where
      start = start_track sel
      cur = cur_track sel
  
  -- | Start and end tracks, from small to large.
  track_range :: Selection -> (TrackNum, TrackNum)
  track_range sel = (Prelude.min track0 track1, Prelude.max track0 track1)
      where (track0, track1) = (start_track sel, cur_track sel)
  
  -- | TrackNums covered by the selection.  Since Selections may have out of
  -- range tracks, I need the number of tracks to generate a list of valid
  -- TrackNums.
  tracknums :: TrackNum -> Selection -> [TrackNum]
  tracknums tracks sel
      | tracks <= 0 = []
      | otherwise = [Num.clamp 0 (tracks-1) start .. Num.clamp 0 (tracks-1) end]
      where (start, end) = track_range sel
  
  min :: Selection -> TrackTime
  min sel = Prelude.min (start_pos sel) (cur_pos sel)
  
  max :: Selection -> TrackTime
  max sel = Prelude.max (start_pos sel) (cur_pos sel)
  
  -- | Start and end points, from small to large.
  range :: Selection -> (TrackTime, TrackTime)
  range sel = (min sel, max sel)
  
  event_orientation :: Selection -> Types.Orientation
  event_orientation sel = case orientation sel of
      Negative -> Types.Negative
      _ -> Types.Positive
  
  duration :: Selection -> TrackTime
  duration sel = abs (start_pos sel - cur_pos sel)
  
  -- | Extend the current track and pos, but keep the start track and pos the
  -- same.
  merge :: Selection -> Selection -> Selection
  merge (Selection strack spos _ _ _) (Selection _ _ ctrack cpos orient) =
      Selection strack spos ctrack cpos orient
  
  -- | Make a selection that covers both the given selections.  It tries to set
  -- start and cur values based on the direction of the merge, assuming you are
  -- starting with the first selection and adding the second.
  union :: Selection -> Selection -> Selection
  union sel1 sel2 = Selection strack spos ctrack cpos (orientation sel2)
      where
      (strack, ctrack) =
          if cur_track sel2 >= cur_track sel1 then se else Tuple.swap se
          where
          se = (Prelude.min s1 s2, Prelude.max e1 e2)
          (s1, e1) = track_range sel1
          (s2, e2) = track_range sel2
      (spos, cpos) = if cur_pos sel2 >= cur_pos sel1 then se else Tuple.swap se
          where
          se = (Prelude.min s1 s2, Prelude.max e1 e2)
          (s1, e1) = range sel1
          (s2, e2) = range sel2
  
  move :: TrackTime -> Selection -> Selection
  move t sel = sel { start_pos = start_pos sel + t, cur_pos = cur_pos sel + t }
  