-- | Contains all necessary types and constructors for citing sources in Drasil.
module Language.Drasil.Data.Citation (
  -- * Types
  CiteField(..), HP(..), CitationKind(..),
  -- * Class
  HasFields(..),
  -- * 'CiteField' Constructors
  -- ** 'People' -> 'CiteField'
  author, editor,
  -- ** 'String' -> 'CiteField'
  address, bookTitle, howPublished, howPublishedU, institution, journal, note,
  organization, publisher, school, series, title, typeField,
  -- FIXME: these should be checked for bounds
  -- ** 'Int' -> 'CiteField'
  chapter, edition, number, volume, year,
  -- ** ['Int'] -> 'CiteField'
  pages,
  -- ** 'Month' -> 'CiteField'
  month,
  -- * Comparators
  compareAuthYearTitle
) where

import Control.Lens (Lens', (^.))
import Data.Maybe (mapMaybe)

import Language.Drasil.People (People, comparePeople)
import Language.Drasil.Data.Date (Month(..))

-- | Fields used in citations.
data CiteField = Address      String
               | Author       People
               | BookTitle    String -- ^ Used for 'InCollection' references only.
               | Chapter      Int
               | Edition      Int
               | Editor       People
               | HowPublished HP     -- ^ Can be published via URL or something else.
               | Institution  String
               | Journal      String
               | Month        Month
               | Note         String
               | Number       Int
               | Organization String
               | Pages        [Int] -- ^ Range of pages (ex1. 1-32; ex2. 7,31,52-55).
               | Publisher    String
               | School       String
               | Series       String
               | Title        String
               | Type         String -- ^ BibTeX "type" field.
               | Volume       Int
               | Year         Int

-- | 'Citation's should have a fields ('CiteField').
class HasFields c where
  -- | Provides a 'Lens' to 'CiteField's.
  getFields :: Lens' c [CiteField]

-- | How something is published. Necessary for URLs to work properly.
data HP = URL String
        | Verb String

-- | External references come in many flavours. Articles, Books, etc.
-- (we are using the types available in Bibtex).
data CitationKind = Article
                  | Book
                  | Booklet
                  | InBook
                  | InCollection
                  | InProceedings
                  | Manual
                  | MThesis
                  | Misc
                  | PhDThesis
                  | Proceedings
                  | TechReport
                  | Unpublished

-- | Smart field constructor for a 'CiteField'.
author, editor :: People -> CiteField
author :: People -> CiteField
author = People -> CiteField
Author
editor :: People -> CiteField
editor = People -> CiteField
Editor

-- | Smart field constructor for a 'CiteField'.
address, bookTitle, institution, journal,
  howPublished, howPublishedU, note, organization, publisher, school, series, title,
  typeField :: String -> CiteField

address :: String -> CiteField
address       = String -> CiteField
Address
bookTitle :: String -> CiteField
bookTitle     = String -> CiteField
BookTitle
howPublished :: String -> CiteField
howPublished  = HP -> CiteField
HowPublished (HP -> CiteField) -> (String -> HP) -> String -> CiteField
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> HP
Verb
-- | URL version of 'howPublished'.
howPublishedU :: String -> CiteField
howPublishedU = HP -> CiteField
HowPublished (HP -> CiteField) -> (String -> HP) -> String -> CiteField
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> HP
URL
institution :: String -> CiteField
institution   = String -> CiteField
Institution
journal :: String -> CiteField
journal       = String -> CiteField
Journal
note :: String -> CiteField
note          = String -> CiteField
Note
organization :: String -> CiteField
organization  = String -> CiteField
Organization
publisher :: String -> CiteField
publisher     = String -> CiteField
Publisher
school :: String -> CiteField
school        = String -> CiteField
School
series :: String -> CiteField
series        = String -> CiteField
Series
title :: String -> CiteField
title         = String -> CiteField
Title
typeField :: String -> CiteField
typeField     = String -> CiteField
Type

-- | Smart field constructor for a 'CiteField'.
chapter, edition, number, volume, year :: Int -> CiteField

chapter :: Int -> CiteField
chapter = Int -> CiteField
Chapter
edition :: Int -> CiteField
edition = Int -> CiteField
Edition
number :: Int -> CiteField
number = Int -> CiteField
Number
volume :: Int -> CiteField
volume = Int -> CiteField
Volume
year :: Int -> CiteField
year = Int -> CiteField
Year

-- | Smart field constructor for a 'CiteField'.
pages :: [Int] -> CiteField
pages :: [Int] -> CiteField
pages = [Int] -> CiteField
Pages

-- | Smart field constructor for a 'CiteField'.
month :: Month -> CiteField
month :: Month -> CiteField
month = Month -> CiteField
Month

-- | Orders two authors. If given two of the exact same authors, year, and
-- title, returns an error.
compareAuthYearTitle :: HasFields c => c -> c -> Ordering
compareAuthYearTitle :: forall c. HasFields c => c -> c -> Ordering
compareAuthYearTitle c
c1 c
c2
  | Ordering
cp Ordering -> Ordering -> Bool
forall a. Eq a => a -> a -> Bool
/= Ordering
EQ  = Ordering
cp
  | Int
y1 Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
/= Int
y2  = Int
y1 Int -> Int -> Ordering
forall a. Ord a => a -> a -> Ordering
`compare` Int
y2
  | Bool
otherwise = String
t1 String -> String -> Ordering
forall a. Ord a => a -> a -> Ordering
`compare` String
t2
  where
    (People
a1, Int
y1, String
t1) = c -> (People, Int, String)
forall c. HasFields c => c -> (People, Int, String)
getAuthorYearTitle c
c1
    (People
a2, Int
y2, String
t2) = c -> (People, Int, String)
forall c. HasFields c => c -> (People, Int, String)
getAuthorYearTitle c
c2

    cp :: Ordering
cp = People -> People -> Ordering
comparePeople People
a1 People
a2

-- | Search for the Author, Year, and Title of a Citation-like data type, and
-- error out if it doesn't have them.
getAuthorYearTitle :: HasFields c => c -> (People, Int, String)
getAuthorYearTitle :: forall c. HasFields c => c -> (People, Int, String)
getAuthorYearTitle c
c = (People
a, Int
y, String
t)
  where
    fs :: [CiteField]
fs = c
c c -> Getting [CiteField] c [CiteField] -> [CiteField]
forall s a. s -> Getting a s a -> a
^. Getting [CiteField] c [CiteField]
forall c. HasFields c => Lens' c [CiteField]
Lens' c [CiteField]
getFields

    justAuthor :: CiteField -> Maybe People
justAuthor (Author People
x) = People -> Maybe People
forall a. a -> Maybe a
Just People
x
    justAuthor CiteField
_          = Maybe People
forall a. Maybe a
Nothing

    as :: [People]
as = (CiteField -> Maybe People) -> [CiteField] -> [People]
forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe CiteField -> Maybe People
justAuthor [CiteField]
fs
    a :: People
a = if Bool -> Bool
not ([People] -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [People]
as) then [People] -> People
forall a. HasCallStack => [a] -> a
head [People]
as else String -> People
forall a. HasCallStack => String -> a
error String
"No author found"

    justYear :: CiteField -> Maybe Int
justYear (Year Int
x) = Int -> Maybe Int
forall a. a -> Maybe a
Just Int
x
    justYear CiteField
_        = Maybe Int
forall a. Maybe a
Nothing

    ys :: [Int]
ys = (CiteField -> Maybe Int) -> [CiteField] -> [Int]
forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe CiteField -> Maybe Int
justYear [CiteField]
fs
    y :: Int
y = if Bool -> Bool
not ([Int] -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [Int]
ys) then [Int] -> Int
forall a. HasCallStack => [a] -> a
head [Int]
ys else String -> Int
forall a. HasCallStack => String -> a
error String
"No year found"

    justTitle :: CiteField -> Maybe String
justTitle (Title String
x) = String -> Maybe String
forall a. a -> Maybe a
Just String
x
    justTitle CiteField
_         = Maybe String
forall a. Maybe a
Nothing

    ts :: [String]
ts = (CiteField -> Maybe String) -> [CiteField] -> [String]
forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe CiteField -> Maybe String
justTitle [CiteField]
fs
    t :: String
t = if Bool -> Bool
not ([String] -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [String]
ts) then [String] -> String
forall a. HasCallStack => [a] -> a
head [String]
ts else String -> String
forall a. HasCallStack => String -> a
error String
"No title found"