{-# LANGUAGE LambdaCase, DeriveGeneric #-}

module Intervals where

import qualified Data.ByteString.Lazy as BS
import Text.Printf
import Data.List hiding (union, intersect)
import Data.Monoid ((<>))
import Data.Yaml (ToJSON, FromJSON)
import GHC.Generics (Generic)
import Data.Function
import Data.Int
import Prelude hiding (subtract)

type Offset = Int64

data Interval = I { from :: Offset, to :: Offset }
    deriving (Show,Generic)

newtype Intervals = Intervals [Interval]
    deriving (Show,Generic)

mkInterval :: Offset -> Offset -> Intervals
mkInterval f t | f < t     = Intervals [I f t]
               | otherwise = Intervals []

fullIntervals :: Offset -> Intervals
fullIntervals len = mkInterval 0 len

nullInterval :: Intervals
nullInterval = Intervals []

size :: Intervals -> Offset
size (Intervals is) = sum [ t - f | I f t <- is ]

isEmpty :: Intervals -> Bool
isEmpty (Intervals is) = null is

subSetOf :: Intervals -> Intervals -> Bool
subSetOf a b = isEmpty (a `subtract` b)

intersects :: Intervals -> Intervals -> Bool
intersects a b = not $ isEmpty (a `intersect` b)

intersect :: Intervals -> Intervals -> Intervals
intersect (Intervals is1) (Intervals is2) = Intervals $ go is1 is2
  where
    go _ [] = []
    go [] _ = []
    go (i1:is1) (i2:is2)
        -- reorder for symmetry
        | to i1 < to i2 = go (i2:is2) (i1:is1)
        -- disjoint
        | from i1 >= to i2 = go (i1:is1) is2
        -- overlapping
        | otherwise = I f' (to i2) : go (i1 { from = to i2} : is1) is2
            where f' = max (from i1) (from i2)


union :: Intervals -> Intervals -> Intervals
union (Intervals is1) (Intervals is2) = Intervals $ go is1 is2
  where
    go is [] = is
    go [] is = is
    go (i1:is1) (i2:is2)
        -- reorder for symmetry
        | to i1 < to i2 = go (i2:is2) (i1:is1)
        -- disjoint
        | from i1 > to i2 = i2 : go (i1:is1) is2
        -- overlapping
        | otherwise  = go (i1 { from = f'} : is1) is2
            where f' = min (from i1) (from i2)

subtract :: Intervals -> Intervals -> Intervals
subtract (Intervals is1) (Intervals is2) = Intervals $ go is1 is2
  where
    go is [] = is
    go [] _  = []
    go (i1:is1) (i2:is2)
        -- i2 past i1
        | to i1 <= from i2 = i1 : go is1 (i2:is2)
        -- i1 past i2
        | to i2 <= from i1 = go (i1:is1) is2
        -- i1 contained in i2
        | from i2 <= from i1 , to i1 <= to i2 = go is1 (i2:is2)
        -- i2 covers beginning of i1
        | from i1 >= from i2 = i1 { from = to i2} : go is1 is2
        -- i2 covers end of i1
        | to i1 <= to i2     = i1 { to = from i2} : go is1 (i2:is2)
        -- i2 in the middle of i1
        | otherwise = I (from i1) (from i2) :
                      I (to i2)   (to i1) : go is1 is2


setZeros :: BS.ByteString -> Intervals -> BS.ByteString
setZeros s (Intervals is) = foldl' go s is
  where
    go s (I f t) = prefix <> zeroes <> postfix
      where
        (tmp, postfix)     = BS.splitAt t s
        (prefix, _discard) = BS.splitAt f tmp
        zeroes = BS.replicate (t-f) 0

ppInterval :: Interval -> String
ppInterval (I f t) = printf "0x%04X-0x%04X" f t

ppIntervals :: Intervals -> String
ppIntervals (Intervals xs) = intercalate " " (map ppInterval xs)

instance FromJSON Interval
instance ToJSON Interval
instance FromJSON Intervals
instance ToJSON Intervals
