{-# LANGUAGE Safe #-}

-- | Nested Ranges are common in practical usage. They appear in such forms as library
-- version numbers ("Version 1.4.5.6" for example).
--
-- It is very useful to be able to compare these ranges to one another. This module exists
-- for the purpose of allowing these comparisons between nested ranges. The module builds
-- upon the basic range concept from other parts of this library.
module Data.Range.NestedRange
  ( NestedRange(..)
  , fromVersion
  , inNestedRange
  ) where

import Data.Range.Range
import Data.Version

-- | The Nested Range is a structure that in a nested form of many ranges where there can
-- be multiple ranges at every level.
--
-- For example, saying that you require a minimum version of 1.2.3 could be represented as:
--
-- @
-- NestedRange [[LowerBoundRange 1],[LowerBoundRange 2],[LowerBoundRange 3]]
-- @
data NestedRange a = NestedRange [[Range a]]
  deriving(Eq, Show)

-- I wanted to know if a nested number of elements are in a given range. That way I can
-- just immediately run a single function and tell things about ranges.

-- | Given a list of nested values and a nested range tell us wether the nested value
-- exists inside the nested range.
--
-- == Examples
--
-- In a simple case:
--
-- @
-- ghci> inNestedRange [2, 8, 3] (NestedRange [[SpanRange 1 2]] :: NestedRange Integer)
-- True
-- (0.01 secs, 558,400 bytes)
-- ghci>
-- @
--
-- Not in the bounds:
--
-- @
-- ghci> inNestedRange [2, 8, 3] (NestedRange [[SpanRange 1 2], [UpperBoundRange 7]] :: NestedRange Integer)
-- False
-- (0.00 secs, 558,896 bytes)
-- ghci>
-- @
--
-- For something based on Data.Version:
--
-- @
-- ghci> version = Version [2, 8, 3] []
-- ghci> upperBound = Version [2, 7] []
-- ghci> inNestedRange (versionBranch version) (fromVersion UpperBoundRange upperBound)
-- False
-- ghci>
-- ghci> inNestedRange (versionBranch version) (fromVersion LowerBoundRange upperBound)
-- True
-- ghci>
-- @
inNestedRange :: Ord a => [a] -> NestedRange a -> Bool
inNestedRange values (NestedRange ranges) = go values ranges
   where
      go :: Ord a => [a] -> [[Range a]] -> Bool
      go [] [] = True -- If there is nothing left then they are equal
      go _  [] = True -- If you have already found the values you have to be in range then they are
      go [] _  = False -- If you have not fully matched it yet then it is not in range.
      go (value : vs) (range : rs) = inRanges range value && go vs rs

-- | This method converts the "Data.Version" datatype into a "NestedRange".
--
-- For example:
--
-- @
-- ghci> fromVersion LowerBoundRange (Version [1, 2, 3] [])
-- NestedRange [[LowerBoundRange 1],[LowerBoundRange 2],[LowerBoundRange 3]]
-- (0.01 secs, 624,736 bytes)
-- ghci>
-- @
fromVersion :: (Int -> Range Int) -> Version -> NestedRange Int
fromVersion bound = NestedRange . fmap (return . bound) . versionBranch