{-# LANGUAGE PatternGuards #-}
-----------------------------------------------------------------------------
-- |
-- Module      :  Text.CSL.Proc
-- Copyright   :  (c) Andrea Rossato
-- License     :  BSD-style (see LICENSE)
--
-- Maintainer  :  Andrea Rossato <andrea.rossato@unitn.it>
-- Stability   :  unstable
-- Portability :  unportable
--
-- This module provides functions for processing the evaluated
-- 'Output' for disambiguation and citation collapsing.
--
-----------------------------------------------------------------------------

module Text.CSL.Proc where

import Control.Arrow ( (&&&), (***) )
import Data.Char ( chr )
import Data.List ( elemIndex, findIndices
                 , sortBy, mapAccumL, nub )
import Data.Maybe
import Data.Ord ( comparing )

import Text.CSL.Eval
import Text.CSL.Proc.Collapse
import Text.CSL.Proc.Disamb
import Text.CSL.Reference
import Text.CSL.Style

-- | With a 'Style', a list of 'Reference's and the list of citation
-- groups (the list of citations with their locator), produce the
-- 'FormattedOutput' for each citation group.
processCitations :: Style -> [Reference] -> [[(String, String)]] -> [[FormattedOutput]]
processCitations s rs
    = citations . citeproc s rs

-- | With a 'Style' and the list of 'Reference's produce the
-- 'FormattedOutput' for the bibliography.
processBibliography :: Style -> [Reference] -> [[FormattedOutput]]
processBibliography s rs
    = bibliography $ citeproc s rs [zip (map citeKey rs) (repeat "")]

-- | With a 'Style', a list of 'Reference's and the list of citation
-- groups (the list of citations with their locator), produce the
-- 'FormattedOutput' for each citation group and the bibliography.
citeproc :: Style -> [Reference] -> [[(String, String)]] -> BiblioData
citeproc s rs cs
    = BD  citsOutput biblioOutput
    where
      -- the list of bib entries, as a list of Reference, with
      -- position, locator and year suffix set.
      biblioRefs   = refsYSuffix . procRefs s . map (getReference rs) .
                     nub . map (id *** const []) . concat $ cs
      biblioOutput = if "disambiguate-add-year-suffix" `elem` getCitDisambOptions s
                     then map (map formatOutput) $ proc addYearSuffix $ procBiblio s biblioRefs
                     else map (map formatOutput) $ procBiblio s biblioRefs
      outputGroups = map $ \(CG fm d os) -> map formatOutput $ outputList fm d os
      citsOutput   = outputGroups $ collapseCitGroups s $ disambCitations s biblioRefs cs

-- | With a 'Style' and a sorted list of 'Reference's produce the
-- evaluated output for the bibliography.
procBiblio :: Style -> [Reference] -> [[Output]]
procBiblio (Style {biblio = mb, csMacros = ms , csTerms = ts}) rs
    = maybe [] process mb
    where
      format  b = uncurry $ evalLayout (bibLayout b) False ts ms (bibOptions b)
      render  b = map (format b) $ chkAut [] rs
      process b = flip map (render b) $ uncurry outputList (layFormat &&& layDelim $ bibLayout b)
      chkAut _ []     = []
      chkAut a (x:xs) = if author x `elem` a
                        then ("subsequent",x) : chkAut             a  xs
                        else ("first"     ,x) : chkAut (author x : a) xs

-- | Given the CSL 'Style' and the list of 'Reference's sort the list
-- according to the 'Style' and assign the citation number to each
-- 'Reference'.
procRefs :: Style -> [Reference] -> [Reference]
procRefs (Style {biblio = mb, csMacros = ms , csTerms = ts}) rs
    = maybe rs process mb
    where
      citNum x = x { citationNumber = maybe 0 ((+) 1 . fromIntegral) . elemIndex x $ rs }
      sort    b = evalSorting ts ms (bibOptions b) (bibSort b)
      process b = map fst . sortBy (comparing snd) . map (citNum &&& sort b) $ rs

-- | Given the list of 'Reference's, compare year and contributors'
-- names and, when they collide, generate a suffix to append to the
-- year for disambiguation.
refsYSuffix :: [Reference] -> [Reference]
refsYSuffix rs
    = update indices
    where
      ryear a b = issued     a == issued     b && issued     a /= []
      auth  a b = author     a == author     b && author     a /= []
      edit  a b = editor     a == editor     b && editor     a /= []
      tran  a b = translator a == translator b && translator a /= []
      comp  a b = (ryear a b && auth a b) ||
                  (ryear a b && edit a b) ||
                  (ryear a b && tran a b)
      update l = fst $ mapAccumL check rs l
      check r (i,s) = flip (,) [] $ take i r ++ [(r !! i){yearSuffix = s}] ++ drop (i + 1) r
      indices  = concatMap (flip zip suffixes) . nub $ map needed rs
      needed r = case () of
                   _ | i <- findIndices (comp r) rs
                     , length i > 1 -> i
                     | otherwise    -> []

suffixes :: [String]
suffixes
    = l ++ [x ++ y | x <- l, y <- l ]
    where
      l = map (return . chr) [97..122]