{-
	Copyright (C) 2018 Dr. Alistair Ward

	This file is part of BishBosh.

	BishBosh 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.

	BishBosh 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 BishBosh.  If not, see <http://www.gnu.org/licenses/>.
-}
{- |
 [@AUTHOR@]	Dr. Alistair Ward

 [@DESCRIPTION@]

	* Defines the value of a single /criterion/, which quantifies the significance of some concept;
	many such criteria may exist, & their weighted-mean drives automated selection of /move/s.

	* Each /criterion-value/ is normalised into the /signed closed unit-interval/.

 [@CAVEAT@]

	* While this data-type could implement the classes 'Num', 'Fractional' & 'Real', these interfaces would allow one to construct invalid instances.
-}

module BishBosh.Attribute.CriterionValue(
-- * Types
-- ** Data-types
	CriterionValue(),
-- * Constants
	zero,
-- * Functions
	calculateWeightedMean,
-- ** Constructor
	mkCriterionValue
) where

import			Control.Arrow((&&&))
import qualified	BishBosh.Attribute.CriterionWeight			as Attribute.CriterionWeight
import qualified	BishBosh.Attribute.WeightedMeanAndCriterionValues	as Attribute.WeightedMeanAndCriterionValues
import qualified	BishBosh.Types						as T
import qualified	Control.Exception
import qualified	Factory.Math.Statistics

-- | Quantifies some criterion; the larger the signed value, the better.
newtype CriterionValue criterionValue	= MkCriterionValue criterionValue deriving (Eq, Show)

instance Num criterionValue => Bounded (CriterionValue criterionValue) where
	minBound	= MkCriterionValue $ negate 1
	maxBound	= MkCriterionValue 1

-- | Smart constructor.
mkCriterionValue :: (
	Num	criterionValue,
	Ord	criterionValue
 ) => criterionValue -> CriterionValue criterionValue
mkCriterionValue criterionValue	= Control.Exception.assert (abs criterionValue <= 1) $ MkCriterionValue criterionValue

-- | Constant.
zero :: Num criterionValue => CriterionValue criterionValue
zero	= MkCriterionValue 0

{- |
	* Calculates the /weighted mean/ of the specified 'CriterionValue's using the corresponding /criterion-weight/s.

	* Also writes individual unweighted 'CriterionValue's, to facilitate post-analysis;
	if the corresponding weight is @0@, evaluation of the criterion is, for efficiency, avoided.

	* CAVEAT: if all weights are @0@, then the result is indeterminate.
-}
calculateWeightedMean :: (
	Fractional	weightedMean,
	Real		criterionValue,
	Real		criterionWeight
 ) => [(CriterionValue criterionValue, Attribute.CriterionWeight.CriterionWeight criterionWeight)] -> Attribute.WeightedMeanAndCriterionValues.WeightedMeanAndCriterionValues weightedMean criterionValue
{-# SPECIALISE calculateWeightedMean :: [(CriterionValue T.CriterionValue, Attribute.CriterionWeight.CriterionWeight T.CriterionWeight)] -> Attribute.WeightedMeanAndCriterionValues.WeightedMeanAndCriterionValues T.WeightedMean T.CriterionValue #-}
calculateWeightedMean assocs	= uncurry Attribute.WeightedMeanAndCriterionValues.mkWeightedMeanAndCriterionValues $ (
	Factory.Math.Statistics.getWeightedMean &&& map fst
 ) [
	(bareCriterionValue, bareCriterionWeight) |
		(MkCriterionValue bareCriterionValue, criterionWeight)	<- assocs,
		let bareCriterionWeight	= Attribute.CriterionWeight.deconstruct criterionWeight,
		bareCriterionWeight /= 0
 ] -- List-comprehension.