{-# LANGUAGE MultiParamTypeClasses #-}
-----------------------------------------------------------------------------
-- Copyright 2019, Advise-Me project team. This file is distributed under 
-- the terms of the Apache License 2.0. For more information, see the files
-- "LICENSE.txt" and "NOTICE.txt", which are included in the distribution.
-----------------------------------------------------------------------------
-- |
-- Maintainer  :  bastiaan.heeren@ou.nl
-- Stability   :  provisional
-- Portability :  portable (depends on ghc)
--
-- Data type for ranges of numbers.
--
-----------------------------------------------------------------------------

module Util.NumberRange (NumberRange, range) where

import Control.Monad (forM)
import Data.Semigroup
import Util.String (split)
import Util.List (groupBy')
import Data.List (sort, nub, intercalate)
import Database.HDBC (SqlValue, fromSql, toSql)
import Data.Convertible.Base (Convertible, safeConvert, convError)
import Ideas.Utils.Prelude (readM)

newtype NumberRange = NumberRange [Int]

range :: [Int] -> NumberRange
range = NumberRange . nub

instance Semigroup NumberRange where
   (NumberRange r1) <> (NumberRange r2) = NumberRange . nub $ r1 ++ r2

instance Monoid NumberRange where
   mempty  = NumberRange []
   mappend = (<>)

instance Show NumberRange where
   show (NumberRange range)
      = intercalate ","
      . map f
      . groupBy' (\x y -> abs (x - y) == 1)
      . sort
      $ range

      where
      f [] = show ""
      f [x] = show x
      f xs = show (head xs) ++ "-" ++ show (last xs)

instance Convertible NumberRange SqlValue where
   safeConvert = Right . toSql . show

instance Convertible SqlValue NumberRange where
   safeConvert sql = either (flip convError sql) Right $ do
      let string = fromSql sql
      ranges <- forM (split ',' string) $ \range ->
         case split '-' range of
            [i] -> do
               i' <- readM i
               return [i']
            [i,j] -> do
               i' <- readM i
               j' <- readM j
               return [i'..j']
            _ -> fail "number range could not be parsed"
      return . NumberRange . concat $ ranges