```-- |
-- Module      :  DobutokO.Poetry.Norms
-- Copyright   :  (c) OleksandrZhabenko 2020
-- Stability   :  Experimental
-- Maintainer  :  olexandr543@yahoo.com
--
-- Helps to order the 7 or less (in the first version the Ukrainian) words (or their concatenations)
-- to obtain (to some extent) suitable for poetry or music text. This module
-- provides several different norms that allow to research the text and
-- to create interesting sequences.

module DobutokO.Poetry.Norms (
-- * Different norms
norm1
, norm2
, norm3
, norm4
, norm5
, norm51
, norm513
, norm54
, norm6
, norm6r
, norm8
-- * Norms combining
, splitNorm
-- ** More complex combining
, combineNorms
, flSplitNorms
, normSplit
, normSplitWeighted
, normSplitShifted
, normSplitWeightedG
) where

import qualified Data.Vector as V
import Data.List ((\\))

-- | The first norm for the list of non-negative 'Int'. For not empty lists equals to the maximum element.
norm1 :: [Int] -> Int
norm1 xs
| null xs = 0
| otherwise = maximum xs
{-# INLINE norm1 #-}

-- | The second norm for the list of non-negative 'Int'. For not empty lists equals to the sum of the elements.
norm2 :: [Int] -> Int
norm2 xs = sum xs
{-# INLINE norm2 #-}

-- | The third norm for the list of non-negative 'Int'. For not empty lists equals to the sum of the doubled maximum element and the rest elements of the list.
norm3 :: [Int] -> Int
norm3 xs
| null xs = 0
| otherwise = maximum xs + sum xs
{-# INLINE norm3 #-}

-- | The fourth norm for the list of non-negative 'Int'. Equals to the sum of the 'norm3' and 'norm2'.
norm4 :: [Int] -> Int
norm4 xs
| null xs = 0
| length xs == 1 = head xs
| otherwise = maximum xs + sum xs + maximum (xs \\ [maximum xs])
{-# INLINE norm4 #-}

-- | The fifth norm for the list of non-negative 'Int'. For not empty lists equals to the sum of the elements quoted with sum of the two most minimum elements.
norm5 :: [Int] -> Int
norm5 xs
| null xs = 0
| length xs == 1 = head xs
| minimum xs == 0 = norm5 . filter (/= 0) \$ xs
| otherwise = sum xs `quot` (minimum xs + minimum (xs \\ [minimum xs]))
{-# INLINE norm5 #-}

-- | The fifth modified norm for the list of non-negative 'Int'. Tries to take into account doubled and prolonged sounds to reduce their influence on the 'norm5'.
norm51 :: [Int] -> Int
norm51 xs
| null xs = 0
| compare (minimum xs) 1 /= GT = let ys = filter (\t -> compare t 1 == GT) xs in
case length ys of
0 -> sum xs
1 -> (3 * sum xs) `quot` (minimum ys + maximum ys)
_ -> (3 * sum xs) `quot` (minimum ys + minimum (ys \\ [minimum ys]))
| length xs == 1 = 1
| otherwise = (3 * sum xs) `quot` (minimum xs + minimum (xs \\ [minimum xs]))
{-# INLINE norm51 #-}

-- | The fifth modified (with three minimums) norm for the list of non-negative 'Int'. Tries to take into account doubled and prolonged sounds
-- to reduce their influence on the 'norm5'.
norm513 :: [Int] -> Int
norm513 xs
| null xs = 0
| compare (minimum xs) 1 /= GT =
let ys = filter (\t -> compare t 1 == GT) xs in
case length ys of
0 -> sum xs
1 -> (3 * sum xs) `quot` (minimum ys + maximum ys)
2 -> let zs = ys \\ [minimum ys] in (3 * sum xs) `quot` (minimum ys + 2 * minimum zs)
_ -> let zs = ys \\ [minimum ys] in (3 * sum xs) `quot` (minimum ys + minimum zs + minimum (zs \\ [minimum zs]))
| otherwise =
let zs = xs \\ [minimum xs] in
case length zs of
0 -> sum xs
1 -> (3 * sum xs) `quot` (minimum zs + maximum zs)
2 -> let ts = zs \\ [minimum zs] in (3 * sum xs) `quot` (minimum zs + 2 * minimum ts)
_ -> let ts = zs \\ [minimum zs] in (3 * sum xs) `quot` (minimum zs + minimum ts + minimum (ts \\ [minimum ts]))
{-# INLINE norm513 #-}

-- | The sixth norm for the list of non-negative 'Int'.
norm6 :: [Int] -> Int
norm6 xs = (norm5 xs * sum xs) `quot` norm3 xs
{-# INLINE norm6 #-}

-- | The sixth modified norm for the list of non-negative 'Int'.
norm6r :: [Int] -> Int
norm6r xs = (norm513 xs * norm3 xs) `quot` sum xs
{-# INLINE norm6r #-}

-- | The fifth-fourth norm for the list of non-negative 'Int'. Tries to generate more suitable for poetry text.
norm54 :: [Int] -> Int
norm54 xs = (norm513 xs * norm6r xs) `quot` norm4 xs
{-# INLINE norm54 #-}

-- | The eigth norm for the list of the non-negative 'Int'. Similarly to 'norm4' is used to quickly evaluate the possibly more diverse texts,
-- which are not simple to quickly pronounce. Is not informative (and therefore, is not intended to be used) for the lists of no more than 1 element.
norm8 :: [Int] -> Int
norm8 xs
| null xs = 0
| length xs == 1 = 1
| otherwise =  (5040 * maximum xs) `quot` minimum xs
{-# INLINE norm8 #-}

-- | Splits a given list of non-negative integers into lists of elements not equal to zero and then applies to them the norms from the 'V.Vector' starting
-- from the last element in the vector right-to-left.
splitNorm :: [Int] -> V.Vector ([Int] -> Int) -> [Int]
splitNorm xs vN
| null (filter (/=0) xs) || V.length vN /= length (filter (== 0) xs) + 1 = []
| otherwise =
let (ys,zs) = break (== 0) xs
zzs = drop 1 zs
in (V.unsafeIndex vN (V.length vN - 1)) ys:splitNorm zzs (V.unsafeSlice 0 (V.length vN - 1) vN)

-- | Applies all the given norms in the 'V.Vector' to the first argument and collects the result as a list.
combineNorms :: [Int] -> V.Vector ([Int] -> Int) -> [Int]
combineNorms xs vN
| null xs = []
| otherwise = V.toList . V.map (\f -> f xs) \$ vN

-- | The flipped variant of the 'combineNorms' (can be more convenient for applications).
flSplitNorms :: V.Vector ([Int] -> Int) -> [Int] -> [Int]
flSplitNorms = flip combineNorms
{-# INLINE flSplitNorms #-}

-- | Using 'combineNorms' and given a group of norms (represented as a 'V.Vector') returns the sum of their applications. These norms are equally important
-- and their order can be volatile.
normSplit :: V.Vector ([Int] -> Int) -> ([Int] -> Int)
normSplit v = sum . flSplitNorms v
{-# INLINE normSplit #-}

-- | Using 'combineNorms' and given a group of norms (represented as a 'V.Vector') returns the sum of their applications. The importance of these norms can be
-- easily controlled by the first function argument. The corresponding greater numbers in the first argument list signify the greater importance of the
-- norm in the 'V.Vector' of norms all being applied (so these numbers are multiplicative weights in the sum). If some of the numbers are equal to zero then
-- the corresponding norm is not taken into account. If some of the numbers are negative then the corresponding norms are weightly subtracted from the sum.
normSplitWeighted :: [Int] -> V.Vector ([Int] -> Int) -> ([Int] -> Int)
normSplitWeighted = normSplitWeightedG (*)
{-# INLINE normSplitWeighted #-}

-- | Similar to 'normSplitWeighted', but uses more complex modification instead of multiplication.
-- Using 'splitNorm' and given a group of norms (represented as a 'V.Vector') returns the sum of their applications. The importance of these norms can be
-- controlled by the first function argument. The corresponding greater numbers in the first argument list signify the greater importance of the
-- norm in the 'V.Vector' of norms all being applied. If some of the numbers are negative then the corresponding norms reduces the sum in some way.
normSplitShifted :: [Int] -> V.Vector ([Int] -> Int) -> ([Int] -> Int)
normSplitShifted = normSplitWeightedG (\x y -> x * (x + y))
{-# INLINE normSplitShifted #-}

-- | Generalization for the 'normSplitWeighted' and 'normSplitShifted' with the possibility to define and use the volatile function that influences the
-- weights for the norms in the 'V.Vector'. This function used in the 'zipWith' is given as the first argument.
normSplitWeightedG :: (Int -> Int -> Int) -> [Int] -> V.Vector ([Int] -> Int) -> ([Int] -> Int)
normSplitWeightedG h xs v
| null xs = \ys -> 0
| otherwise = let g ts = zipWith h (flSplitNorms v ts) xs in sum . g
```