{-
	Copyright (C) 2013-2015 Dr. Alistair Ward

	This program is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program.  If not, see <http://www.gnu.org/licenses/>.
-}
{- |
 [@AUTHOR@]	Dr. Alistair Ward

 [@DESCRIPTION@]

	* Defines options for program-operation.

	* Defines an appropriate default value, which is expected to be over-ridden on the command-line.

	* Self-validates.
-}

module FishFood.Data.CommandOptions(
-- * Types
-- ** Type-synonyms
        BinSizeDelta,
-- ** Data-types
        CommandOptions(
--		MkCommandOptions,
                getBinSizeDelta,
                getDeriveProbabilityMassFunction,
                getNDecimalDigits,
                getVerbosity
        ),
-- * Functions
-- ** Constructor
        mkCommandOptions,
-- ** Mutators
        setBinSizeIncrement,
        setBinSizeRatio
) where

import qualified        Data.Default
import qualified        Data.Maybe
import qualified        Distribution.Verbosity
import qualified        FishFood.Data.File      as Data.File
import qualified        ToolShed.SelfValidate

-- | Either an arithmetic size-increase for which there's a default, or a geometric size-ratio.
type BinSizeDelta ratio = Either (Maybe Data.File.FileSize) ratio

-- | Declares a record to contain command-line options.
data CommandOptions ratio       = MkCommandOptions {
        getBinSizeDelta                         :: BinSizeDelta ratio,                  -- ^ Either the arithmetic size-increase (defaulting to one standard-deviation), or the geometric size-ratio, of the sequence of bins into which files are categorized.
        getDeriveProbabilityMassFunction        :: Bool,                                -- ^ Whether to derive the "Probability mass function" rather than the "Frequency-distribution".
        getNDecimalDigits                       :: Int,                                 -- ^ The precision to which fractional data is displayed.
        getVerbosity                            :: Distribution.Verbosity.Verbosity     -- ^ The threshold for ancillary information-output.
} deriving Show

instance Data.Default.Default (CommandOptions ratio)    where
        def = MkCommandOptions {
                getBinSizeDelta                         = Left Nothing, -- Interpreted as one standard-deviation.
                getDeriveProbabilityMassFunction        = False,
                getNDecimalDigits                       = 3,
                getVerbosity                            = Distribution.Verbosity.normal
        }

instance (Num ratio, Ord ratio, Show ratio) => ToolShed.SelfValidate.SelfValidator (CommandOptions ratio)       where
        getErrors commandOptions@MkCommandOptions {
                getBinSizeDelta                         = binSizeDelta,
                getDeriveProbabilityMassFunction        = deriveProbabilityMassFunction,
                getNDecimalDigits                       = nDecimalDigits
        } = map snd $ filter fst [
                (
                        either (Data.Maybe.maybe False (<= 0)) (<= 1) binSizeDelta,
                        "either the bin-size's arithmetic increase must exceed zero, or it's geometric ratio must exceed one; " ++ show commandOptions ++ "."
                ), (
                        deriveProbabilityMassFunction && nDecimalDigits < 1,
                        "the number of decimal digits must exceed zero to adequately represent probabilities; " ++ show commandOptions ++ "."
                ),
                let
                        maxNDecimalDigits       = floor $ fromIntegral (
                                floatDigits (
                                        undefined :: Double     -- CAVEAT: the actual type could be merely 'Float', but that's currently unknown.
                                )
                         ) * (logBase 10 2 :: Double)
                in (
                        nDecimalDigits > maxNDecimalDigits,
                        "the number of decimal digits shouldn't exceed " ++ show maxNDecimalDigits ++ "; " ++ show commandOptions ++ "."
                )
         ]

-- | Smart constructor.
mkCommandOptions :: (Num ratio, Ord ratio, Show ratio) => BinSizeDelta ratio -> Bool -> Int -> Distribution.Verbosity.Verbosity -> CommandOptions ratio
mkCommandOptions binSizeDelta deriveProbabilityMassFunction nDecimalDigits verbosity
        | ToolShed.SelfValidate.isValid commandOptions  = commandOptions
        | otherwise                                     = error $ "FishFood.Data.CommandOptions.mkCommandOptions:\t" ++ ToolShed.SelfValidate.getFirstError commandOptions
        where
                commandOptions  = MkCommandOptions binSizeDelta deriveProbabilityMassFunction nDecimalDigits verbosity

-- | Mutator.
setBinSizeIncrement :: Data.File.FileSize -> CommandOptions ratio -> CommandOptions ratio
setBinSizeIncrement fileSize commandOptions     = commandOptions { getBinSizeDelta = Left $ Just fileSize }

-- | Mutator.
setBinSizeRatio :: ratio -> CommandOptions ratio -> CommandOptions ratio
setBinSizeRatio ratio commandOptions    = commandOptions { getBinSizeDelta = Right ratio }