{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE Rank2Types #-}
{-# LANGUAGE FlexibleContexts #-}
{-# OPTIONS_GHC -Wall #-}
{-|
Maintainer  :  nvd124@gmail.com
Stability   :  unstable
Portability :  portable

This module is for Family Trees.

It uses lenses for the manipulation of people. For the usage of lenses, see
"Data.Lens.Lazy"

It is reccomended to use "Data.Binary" to do saving and loading.


-}
module Data.FamilyTree
 (
 -- * Types
 -- ** Main types
 Person(..),
 name,
 attributes,
 birthdate,
 birthplace,
 deathdate,
 deathplace,
 attendedEvents,
 Family(..),
 head1,
 head2,
 children,
 relationFrom,
 relationTo,
 relationship,
 Event(..),
 eventInfo,
 eventDate,
 eventAttendees,
 
 FamilyTree(..),
 treeName,
 people,
 families,
 events,
 -- ** ID types
 -- $ids
 PersonID(..),
 FamilyID(..),
 EventID(..),
 -- ** Other types
 Location(..),
 Relationship(..),
 -- * Functions
 -- ** Creation
 newTree,
 addPerson,
 addFamily,
 addEvent,
 -- ** Manipulation
 traversePerson,
 traverseFamily,
 traverseEvent,
 -- ** Destruction
 deletePerson,
 deleteFamily,
 deleteEvent,
 -- * Utility functions
 partialDateFromYear,
 partialDateFromMonth,
 partialDateFromDay
) where

import Control.Applicative (Applicative(..), (<$>), Alternative(..))
import Control.Lens hiding (children)

import Data.Binary (Word8, Binary(..), getWord8)
import Data.Function (on)
import Data.HashMap.Strict (HashMap)
import qualified Data.HashMap.Strict as HM
import Data.Maybe (listToMaybe)
import Data.Monoid (Monoid(..), First(..))
import Data.IntMap (IntMap)
import qualified Data.IntMap as IM
import Data.IntSet (IntSet)
import qualified Data.IntSet as IS
import Data.Text (Text)
import qualified Data.Text as T
import Data.Text.Encoding (encodeUtf8, decodeUtf8)
import Data.Time (Day(..), fromGregorian, gregorianMonthLength)

import Numeric.Interval (Interval)
import qualified Numeric.Interval as I

-- $ids
-- The various ID types represent an identifier for a person, family, or event. 
-- While the constructors are exported, it is probably better to use the
-- various 'Traversal's for manipulation, as they echo the changes around the
-- tree automatically.
newtype PersonID = PersonID {getPersonID :: Int} deriving (Eq, Ord, Show, Read)

instance Wrapped Int Int PersonID PersonID where
  wrapped = iso PersonID getPersonID

newtype FamilyID = FamilyID {getFamilyID :: Int} deriving (Eq, Ord, Show, Read)

instance Wrapped Int Int FamilyID FamilyID where
  wrapped = iso FamilyID getFamilyID

newtype EventID = EventID {getEventID :: Int} deriving (Eq, Ord, Show, Read)

instance Wrapped Int Int EventID EventID where
  wrapped = iso EventID getEventID

-- | The Location type. Either a coordinate or a placename.  
data Location = Coord Double Double | PlaceName Text deriving (Eq, Show)

-- | The Relationship type. Marriage is the default for similarity to GEDCOM.
data Relationship = Marriage | Other Text deriving (Eq, Show)

type PartialDate = Interval Day

partialDateFromYear :: Integer -> PartialDate
partialDateFromYear n = I.I (fromGregorian n 1 1) (fromGregorian n 12 31)

partialDateFromMonth :: Integer -> Int -> PartialDate
partialDateFromMonth y m = I.I (fromGregorian y m 1) . 
    fromGregorian y m $ gregorianMonthLength y m

partialDateFromDay :: Integer -> Int -> Int -> PartialDate
partialDateFromDay y m d = I.singleton $ fromGregorian y m d

-- | The basic type for a person. 'Nothing' meaning unknown (or otherwise 
-- non-existent, for intance a death date for someone still alive) is a
-- convention used throughout this library.
data Person = Person
  {_name :: Maybe Text
  ,_birthdate :: Maybe PartialDate
  ,_birthplace :: Maybe Location
  ,_deathdate :: Maybe PartialDate
  ,_deathplace :: Maybe Location
  ,_attributes :: HashMap Text Text
  ,_attendedEvents :: IntSet
  } deriving (Eq, Show)
             
makeLenses ''Person

-- | The basic type for a family. Which person is head1 and which is head2 is
-- arbitrary, but try to use a consistent rule
data Family = Family
  {_head1 :: Maybe PersonID
  ,_head2 :: Maybe PersonID
  ,_relationship :: Maybe Relationship
  ,_relationFrom :: Maybe PartialDate
  ,_relationTo :: Maybe PartialDate
  ,_children :: IntSet
  } deriving (Eq, Show)
  
makeLenses ''Family

-- | The basic type for an event. For example:
--
-- @
--   Event {
--     _eventInfo = \"Battle of Agincourt\"
--     _eventDate = fromGregorianValid 1415 10 25
--     _eventAttendees = IM.empty
--         }
-- @
data Event = Event 
  {_eventInfo :: Text
  ,_eventDate :: Maybe PartialDate
  ,_eventAttendees :: IntSet
  } deriving (Eq, Show)

makeLenses ''Event

-- | The core structure of a family tree.
data FamilyTree = FamilyTree
  {_treeName :: Text
  ,_people :: IntMap Person
  ,_families :: IntMap Family
  ,_events :: IntMap Event
  } deriving (Eq, Show)
  
makeLenses ''FamilyTree

instance Monoid Person where
  mempty = Person {
    _name = Nothing,
    _birthdate = Nothing,
    _birthplace = Nothing,
    _deathdate = Nothing,
    _deathplace = Nothing,
    _attributes = HM.empty,
    _attendedEvents = IS.empty
    }
  p1 `mappend` p2 = Person {
    _name = ((<|>) `on` _name) p1 p2,
    _birthdate = ((<|>) `on` _birthdate) p1 p2,
    _birthplace = ((<|>) `on` _birthplace) p1 p2,
    _deathdate = ((<|>) `on` _deathdate) p1 p2,
    _deathplace = ((<|>) `on` _deathplace) p1 p2,
    _attributes = (HM.union `on` _attributes) p1 p2,
    _attendedEvents = (IS.union `on` _attendedEvents) p1 p2
    }

instance Monoid Family where
  mempty = Family {
    _head1 = Nothing,
    _head2 = Nothing,
    _relationship = Nothing,
    _relationFrom = Nothing,
    _relationTo = Nothing,
    _children = IS.empty
    }
  f1 `mappend` f2 = Family {
    _head1 = getFirst $ (mappend `on` First . _head1) f1 f2,
    _head2 = getFirst $ (mappend `on` First . _head2) f1 f2,
    _relationship = getFirst $ (mappend `on` First . _relationship) f1 f2,
    _relationFrom = getFirst $ (mappend `on` First . _relationFrom) f1 f2,
    _relationTo = getFirst $ (mappend `on` First . _relationTo) f1 f2,
    _children = (IS.union `on` _children) f1 f2
    }

instance Monoid Event where
  mempty = Event {
    _eventInfo = T.empty,
    _eventDate = Nothing,
    _eventAttendees = IS.empty
    }
  e1 `mappend` e2 = Event {
    _eventInfo = (T.append `on` _eventInfo) e1 e2,
    _eventDate = getFirst $ (mappend `on` First . _eventDate) e1 e2,
    _eventAttendees = (IS.union `on` _eventAttendees) e1 e2
    }

instance Binary Location where
  put (Coord x y) = do
    put (0 :: Word8)
    put x
    put y
  put (PlaceName t) = do
    put (1 :: Word8)
    put (encodeUtf8 t)
  get = do
    tag <- getWord8
    case tag of
      0 -> do
        x <- get
        y <- get
        return $ Coord x y
      _ -> PlaceName . decodeUtf8 <$> get

instance Binary Relationship where
  put (Other t) = do
    put (0 :: Word8)
    put (encodeUtf8 t)
  put Marriage = put (1 :: Word8)
  get = do
    tag <- getWord8
    case tag of
      0 -> Other . decodeUtf8 <$> get
      _ -> return Marriage

instance Binary Person where
  put person = do
    put (encodeUtf8 <$> _name person)
    put (toModifiedJulianDay . I.inf <$> _birthdate person)
    put (toModifiedJulianDay . I.sup <$> _birthdate person)
    put (_birthplace person)
    put (toModifiedJulianDay . I.inf <$> _deathdate person)
    put (toModifiedJulianDay . I.sup <$> _deathdate person)
    put (_deathplace person)
    put (map (both %~ encodeUtf8) . HM.toList $ _attributes person)
    put (_attendedEvents person)
  get = do
    n <- get
    bdi <- get
    bds <- get
    bp <- get
    ddi <- get
    dds <- get
    dp <- get
    a <- get
    e <- get
    return Person
      {_name = fmap decodeUtf8 n
      ,_birthdate = I.I <$> fmap ModifiedJulianDay bdi <*>
        fmap ModifiedJulianDay bds
      ,_birthplace = bp
      ,_deathdate = I.I <$> fmap ModifiedJulianDay ddi <*>
        fmap ModifiedJulianDay dds
      ,_deathplace = dp
      ,_attributes = HM.fromList $ map (both %~ decodeUtf8) a
      ,_attendedEvents = e
      }

instance Binary Family where
  put fam = do
    put $ getPersonID <$> _head1 fam
    put $ getPersonID <$> _head2 fam
    put $ _relationship fam
    put $ toModifiedJulianDay . I.inf <$> _relationFrom fam
    put $ toModifiedJulianDay . I.sup <$> _relationFrom fam
    put $ toModifiedJulianDay . I.inf <$> _relationTo fam
    put $ toModifiedJulianDay . I.sup <$> _relationTo fam
    put $ _children fam
  get = do
    h1 <- get
    h2 <- get
    r <- get
    rfi <- get
    rfs <- get
    rti <- get
    rts <- get
    c <- get
    return Family
      {_head1 = PersonID <$> h1
      ,_head2 = PersonID <$> h2
      ,_relationship = r
      ,_relationFrom = I.I <$> fmap ModifiedJulianDay rfi <*> fmap ModifiedJulianDay rfs
      ,_relationTo = I.I <$> fmap ModifiedJulianDay rti <*> fmap ModifiedJulianDay rts
      ,_children = c
      }
      
instance Binary Event where
  put evnt = do
    put . encodeUtf8 $ _eventInfo evnt
    put $ toModifiedJulianDay . I.inf <$> _eventDate evnt
    put $ toModifiedJulianDay . I.sup <$> _eventDate evnt
    put $ _eventAttendees evnt
  get = do
    n <- get
    di <- get
    ds <- get
    a <- get
    return Event
      {_eventInfo = decodeUtf8 n
      ,_eventDate = I.I <$> fmap ModifiedJulianDay di <*> fmap ModifiedJulianDay ds
      ,_eventAttendees = a
      }

instance Binary FamilyTree where
  put tree = do
    put $ encodeUtf8 $ _treeName tree
    put $ _people tree
    put $ _families tree
    put $ _events tree
  get = do
    n <- get
    p <- get
    f <- get
    e <- get
    return FamilyTree
      {_treeName = decodeUtf8 n
      ,_people = p
      ,_families = f
      ,_events = e
      }

-- | Constructs a 'Traversal' for the manipulation of a person in a family tree, from
-- that person's ID. 
traversePerson :: PersonID -> SimpleIndexedTraversal PersonID FamilyTree Person
traversePerson (PersonID n) = indexed $
  \f familyTree -> case familyTree ^. people . at n of
    Nothing -> pure familyTree
    Just oldPerson -> 
      let newPerson_ = f (PersonID n) oldPerson
          newEvents_ = flip (IS.difference `on` _attendedEvents) oldPerson
            <$> newPerson_
          oldEvents_ =      (IS.difference `on` _attendedEvents) oldPerson
            <$> newPerson_
      in alterPerson familyTree <$> newPerson_ <*> newEvents_ <*> oldEvents_
  where
    alterPerson familyTree newPerson =
      IS.foldr (\i -> events . _at i . eventAttendees %~ IS.delete n) .
      IS.foldr (\i -> events . _at i . eventAttendees %~ IS.insert n) (
      people . _at n .~ newPerson $ familyTree)

-- | Constructs a lens for the manipulation of a family in a family tree, from
-- that family's ID.
traverseFamily :: FamilyID -> SimpleIndexedTraversal FamilyID FamilyTree Family
traverseFamily (FamilyID n) = indexed $
  \f familyTree -> case familyTree ^. families . at n of
    Nothing -> pure familyTree
    Just oldFamily -> let newFamily_ = f (FamilyID n) oldFamily
                      in alterFamily familyTree <$> newFamily_
  where
    alterFamily familyTree newFamily =
      familyTree & families . _at n .~ newFamily

-- | Constructs a 'Traversal' for the manipulation of an event in a family tree, from
-- that event's ID.      
traverseEvent :: EventID -> SimpleIndexedTraversal EventID FamilyTree Event
traverseEvent (EventID n) = indexed $
  \f familyTree -> case familyTree ^. events . at n of
    Nothing -> pure familyTree
    Just oldEvent ->
      let newEvent_  = f (EventID n) oldEvent
          oldPeople_ =      (IS.difference `on` _eventAttendees) oldEvent
            <$> newEvent_
          newPeople_ = flip (IS.difference `on` _eventAttendees) oldEvent
             <$> newEvent_
      in alterEvent familyTree <$> newEvent_ <*> newPeople_ <*> oldPeople_
  where
    alterEvent familyTree newEvent =
      IS.foldr (\i -> people . _at i . attendedEvents %~ IS.delete n) .
      IS.foldr (\i -> people . _at i . attendedEvents %~ IS.insert n) (
      events . _at n .~ newEvent $ familyTree)

-- | Adds a person with minimal information, returning the updated family tree
-- and the ID of the new person.  
addPerson :: FamilyTree -> (PersonID, FamilyTree)
addPerson familyTree =
  let n = maybe 0 fst $
          listToMaybe $
          dropWhile (uncurry (==)) $
          zip [1 ..] $ IM.keys $ _people familyTree
  in (PersonID n, people . at n ?~ mempty $ familyTree)

-- | Adds a family with minimal information, returning the updated family tree
-- and the ID of the new family.  
addFamily :: FamilyTree -> (FamilyID, FamilyTree)
addFamily familyTree =
  let n = maybe 0 fst $
          listToMaybe $
          dropWhile (uncurry (==)) $
          zip [1 ..] $ IM.keys $ _families familyTree
  in (FamilyID n, families . at n ?~ mempty $ familyTree)

-- | Adds an event with minimal information, returning the updated family tree
-- and the ID of the new event.
addEvent :: FamilyTree -> (EventID, FamilyTree)
addEvent familyTree =
  let n = maybe 0 fst $
          listToMaybe $
          dropWhile (uncurry (==)) $
          zip [1 ..] $ IM.keys $ _events familyTree
  in (EventID n, events . at n ?~ mempty $ familyTree)

-- | Deletes a person from the family tree, removing all references to them.  
deletePerson :: PersonID -> FamilyTree -> FamilyTree
deletePerson (PersonID n) familyTree =
  familyTree &
  people . at n .~ Nothing &
  families %~ IM.map (
    \fam -> fam &
            head1 %~ (id & resultAt (Just $ PersonID n) .~ Nothing) &
            head2 %~ (id & resultAt (Just $ PersonID n) .~ Nothing) &
            children . contains n .~ False
            ) &
  events %~ IM.map (eventAttendees . contains n .~ False)

-- | Deletes a family from the family tree, removing all references to it.    
deleteFamily :: FamilyID -> FamilyTree -> FamilyTree
deleteFamily (FamilyID n) = families . at n .~ Nothing

-- | Deletes an event from the family tree, removing all references to it.
deleteEvent :: EventID -> FamilyTree -> FamilyTree
deleteEvent (EventID n) familyTree =
  let relevantPeople = _eventAttendees (_events familyTree IM.! n)
  in familyTree
       {_events = IM.delete n $ _events familyTree
       ,_people = IS.foldr (IM.adjust
         (\p -> p {_attendedEvents = IS.delete n $ _attendedEvents p}))
         (_people familyTree) relevantPeople
       }           

-- | Creates a new tree with a given name.       
newTree :: Text -> FamilyTree
newTree n = FamilyTree
  {_treeName = n
  ,_people = IM.empty
  ,_families = IM.empty
  ,_events = IM.empty
  }