{-# LANGUAGE Safe #-}

-- This module contains every function that purely performs operations on spans.
module Data.Range.Spans where

import Data.List (sortBy, insertBy)
import Data.Ord (comparing)

import Data.Range.Util

-- Assume that both inputs are sorted spans
insertionSortSpans :: (Ord a) => [(a, a)] -> [(a, a)] -> [(a, a)]
insertionSortSpans = insertionSort (comparing fst)

spanCmp :: Ord a => (a, a) -> (a, a) -> Ordering
spanCmp x@(xlow, xhigh) y@(ylow, _) = if isBetween xlow y || isBetween ylow x
   then EQ
   else if xhigh < ylow then LT else GT

intersectSpans :: (Ord a) => [(a, a)] -> [(a, a)] -> [(a, a)]
intersectSpans (x@(xlow, xup) : xs) (y@(ylow, yup) : ys) =
   case spanCmp x y of
      EQ -> (max xlow ylow, min xup yup) : if xup < yup
         then intersectSpans xs (y : ys)
         else intersectSpans (x : xs) ys
      LT -> intersectSpans xs (y : ys)
      GT -> intersectSpans (x : xs) ys
intersectSpans _ _ = []

insertSpan :: Ord a => (a, b) -> [(a, b)] -> [(a, b)]
insertSpan = insertBy (comparing fst)

sortSpans :: (Ord a) => [(a, a)] -> [(a, a)]
sortSpans = sortBy (comparing fst)

-- Assume that you are given a sorted list of spans
joinSpans :: (Ord a, Enum a) => [(a, a)] -> [(a, a)]
joinSpans (f@(a, b) : s@(x, y) : xs) =
   if succ b == x
      then joinSpans $ (a, y) : xs
      else f : joinSpans (s : xs)
joinSpans xs = xs

-- Assume that you are given a sorted list of spans
unionSpans :: Ord a => [(a, a)] -> [(a, a)]
unionSpans (f@(a, b) : s@(x, y) : xs) = if isBetween x f
   then unionSpans ((a, max b y) : xs)
   else f : unionSpans (s : xs)
unionSpans xs = xs

-- Assume that you are given a sorted and joined list of spans
invertSpans :: (Ord a, Enum a) => [(a, a)] -> [(a, a)]
invertSpans ((_, x) : s@(y, _) : xs) = (succ x, pred y) : invertSpans (s : xs)
invertSpans _ = []

hasOverlaps :: (Ord a, Enum a) => [(a, a)] -> Bool
hasOverlaps xs = any isOverlapping (pairs xs)
   where
      isOverlapping ((x, y), (a, b)) = isBetween x (pred a, succ b) || isBetween a (pred x, succ y)