module Data.FamilyTree
(
Person(..),
name,
attributes,
birthdate,
birthplace,
deathdate,
deathplace,
Family(..),
head1,
head2,
children,
relationFrom,
relationTo,
relationship,
FamilyTree(..),
treeName,
people,
families,
PartialDate,
Location(..),
Relationship(..),
newTree,
addPerson,
addFamily,
FamilyTreePart (..),
deletePerson,
deleteFamily,
partialDateFromYear,
partialDateFromMonth,
partialDateFromDay
) where
import Control.Applicative (Applicative(..), (<$>), Alternative((<|>)))
import Control.Lens hiding (children, (...))
import Data.Binary (Binary(..), putWord8, getWord8)
import Data.Function (on)
import Data.Hashable (Hashable(..))
import Data.HashMap.Strict (HashMap)
import qualified Data.HashMap.Strict as HM
import Data.Monoid (Monoid(..), First(..))
import Data.IntSet (IntSet)
import qualified Data.IntSet as IS
import Data.Table
import Data.String
import Data.Text (Text)
import Data.Text.Encoding (encodeUtf8, decodeUtf8)
import Data.Time (Day(..), fromGregorian, gregorianMonthLength)
import GHC.Generics (Generic)
import Numeric.Interval (Interval, (...))
import qualified Numeric.Interval as I
data Location = Coord Double Double | PlaceName Text deriving (Eq, Show, Generic)
instance IsString Location where
fromString = PlaceName . fromString
data Relationship = Marriage | Other Text deriving (Eq, Show, Generic)
type PartialDate = Interval Day
partialDateFromYear :: Integer -> PartialDate
partialDateFromYear n = fromGregorian n 1 1 ... fromGregorian n 12 31
partialDateFromMonth :: Integer -> Int -> PartialDate
partialDateFromMonth y m = (...) (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
data Person = Person
{_personId :: Int
,_name :: Maybe Text
,_birthdate :: Maybe PartialDate
,_birthplace :: Maybe Location
,_deathdate :: Maybe PartialDate
,_deathplace :: Maybe Location
,_attributes :: HashMap Text Text
} deriving (Eq, Show, Generic)
makeLenses ''Person
instance Tabular Person where
type PKT Person = Int
data Tab Person i = PersonTab (i Primary Int) (i SupplementalHash (Maybe Text)) (i Supplemental (Maybe PartialDate))
(i SupplementalHash (Maybe Location)) (i Supplemental (Maybe PartialDate))
(i SupplementalHash (Maybe Location))
data Key k Person t where
PersonID :: Key Primary Person Int
Name :: Key SupplementalHash Person (Maybe Text)
BirthDate :: Key Supplemental Person (Maybe PartialDate)
BirthPlace :: Key SupplementalHash Person (Maybe Location)
DeathDate :: Key Supplemental Person (Maybe PartialDate)
DeathPlace :: Key SupplementalHash Person (Maybe Location)
fetch PersonID = _personId
fetch Name = _name
fetch BirthDate = _birthdate
fetch BirthPlace = _birthplace
fetch DeathDate = _deathdate
fetch DeathPlace = _deathplace
primary = PersonID
primarily PersonID r = r
mkTab f = PersonTab <$> f PersonID <*> f Name <*> f BirthDate <*> f BirthPlace
<*> f DeathDate <*> f DeathPlace
ixTab (PersonTab i n bd bp dd dp) k = case k of
PersonID -> i
Name -> n
BirthDate -> bd
BirthPlace -> bp
DeathDate -> dd
DeathPlace -> dp
forTab (PersonTab i n bd bp dd dp) f =
PersonTab <$> f PersonID i <*> f Name n <*> f BirthDate bd <*> f BirthPlace bp
<*> f DeathDate dd <*> f DeathPlace dp
autoTab = autoIncrement personId
data Family = Family
{_familyId :: Int
,_head1 :: Maybe Int
,_head2 :: Maybe Int
,_relationship :: Maybe Relationship
,_relationFrom :: Maybe PartialDate
,_relationTo :: Maybe PartialDate
,_children :: IntSet
} deriving (Eq, Show, Generic)
makeLenses ''Family
instance Tabular Family where
type PKT Family = Int
data Tab Family i = FamilyTab (i Primary Int) (i Supplemental (Maybe Int)) (i Supplemental (Maybe Int))
(i SupplementalHash (Maybe Relationship)) (i Supplemental (Maybe PartialDate))
(i Supplemental (Maybe PartialDate)) (i InvertedInt (IntSet))
data Key k Family t where
FamilyID :: Key Primary Family Int
Head1 :: Key Supplemental Family (Maybe Int)
Head2 :: Key Supplemental Family (Maybe Int)
Relationship :: Key SupplementalHash Family (Maybe Relationship)
RelationFrom :: Key Supplemental Family (Maybe PartialDate)
RelationTo :: Key Supplemental Family (Maybe PartialDate)
Children :: Key InvertedInt Family IntSet
fetch FamilyID = _familyId
fetch Head1 = _head1
fetch Head2 = _head2
fetch Relationship = _relationship
fetch RelationFrom = _relationFrom
fetch RelationTo = _relationTo
fetch Children = _children
primary = FamilyID
primarily FamilyID r = r
mkTab f = FamilyTab <$> f FamilyID <*> f Head1 <*> f Head2 <*> f Relationship
<*> f RelationFrom <*> f RelationTo <*> f Children
ixTab (FamilyTab i h1 h2 r rf rt c) k = case k of
FamilyID -> i
Head1 -> h1
Head2 -> h2
Relationship -> r
RelationFrom -> rf
RelationTo -> rt
Children -> c
forTab (FamilyTab i h1 h2 r rf rt c) f =
FamilyTab <$> f FamilyID i <*> f Head1 h1 <*> f Head2 h2 <*> f Relationship r
<*> f RelationFrom rf <*> f RelationTo rt <*> f Children c
autoTab = autoIncrement familyId
data FamilyTree = FamilyTree
{_treeName :: Text
,_people :: Table Person
,_families :: Table Family
} deriving (Eq, Show, Generic)
makeLenses ''FamilyTree
instance Monoid Person where
mempty = Person {
_personId = 0,
_name = Nothing,
_birthdate = Nothing,
_birthplace = Nothing,
_deathdate = Nothing,
_deathplace = Nothing,
_attributes = HM.empty
}
p1 `mappend` p2 = Person {
_personId = _personId p1,
_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
}
instance Monoid Family where
mempty = Family {
_familyId = 0,
_head1 = Nothing,
_head2 = Nothing,
_relationship = Nothing,
_relationFrom = Nothing,
_relationTo = Nothing,
_children = IS.empty
}
f1 `mappend` f2 = Family {
_familyId = _familyId f1,
_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 Binary Location where
put (Coord x y) = do
putWord8 0
put x
put y
put (PlaceName t) = do
putWord8 1
put (encodeUtf8 t)
get = do
tag <- getWord8
case tag of
0 -> do
x <- get
y <- get
return $ Coord x y
_ -> PlaceName . decodeUtf8 <$> get
instance Hashable Location
instance Binary Relationship where
put (Other t) = do
putWord8 0
put (encodeUtf8 t)
put Marriage = putWord8 1
get = do
tag <- getWord8
case tag of
0 -> Other . decodeUtf8 <$> get
_ -> return Marriage
instance Hashable Relationship
instance Binary Person where
put person = do
put (_personId person)
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)
get = do
i <- get
n <- get
bdi <- get
bds <- get
bp <- get
ddi <- get
dds <- get
dp <- get
a <- get
return Person
{_personId = i
,_name = fmap decodeUtf8 n
,_birthdate = (...) <$> fmap ModifiedJulianDay bdi <*>
fmap ModifiedJulianDay bds
,_birthplace = bp
,_deathdate = (...) <$> fmap ModifiedJulianDay ddi <*>
fmap ModifiedJulianDay dds
,_deathplace = dp
,_attributes = HM.fromList $ map (both %~ decodeUtf8) a
}
instance Binary Family where
put fam = do
put $ _familyId fam
put $ _head1 fam
put $ _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
f <- get
h1 <- get
h2 <- get
r <- get
rfi <- get
rfs <- get
rti <- get
rts <- get
c <- get
return Family
{_familyId = f
,_head1 = h1
,_head2 = h2
,_relationship = r
,_relationFrom = (...) <$> fmap ModifiedJulianDay rfi <*> fmap ModifiedJulianDay rfs
,_relationTo = (...) <$> fmap ModifiedJulianDay rti <*> fmap ModifiedJulianDay rts
,_children = c
}
instance Binary FamilyTree where
put tree = do
put $ encodeUtf8 $ _treeName tree
put $ _people tree
put $ _families tree
get = do
n <- get
p <- get
f <- get
return FamilyTree
{_treeName = decodeUtf8 n
,_people = p
,_families = f
}
class FamilyTreePart part where
accessFT :: Int -> IndexedTraversal' Int FamilyTree part
instance FamilyTreePart Person where
accessFT n f familyTree = case familyTree ^. people . at n of
Nothing -> pure familyTree
Just oldPerson ->
let newPerson_ = indexed f n oldPerson
in alterPerson <$> newPerson_
where
alterPerson newPerson = people . ix n .~ newPerson $ familyTree
instance FamilyTreePart Family where
accessFT n f familyTree = case familyTree ^. families . at n of
Nothing -> pure familyTree
Just oldFamily -> let newFamily_ = indexed f n oldFamily
in alterFamily <$> newFamily_
where
alterFamily newFamily =
familyTree & families . ix n .~ newFamily
addPerson :: FamilyTree -> (Int, FamilyTree)
addPerson = people %%~ (_1 %~ _personId) . insert' mempty
addFamily :: FamilyTree -> (Int, FamilyTree)
addFamily = families %%~ (_1 %~ _familyId) . insert' mempty
deletePerson :: Int -> FamilyTree -> FamilyTree
deletePerson n familyTree =
familyTree &
people . at n .~ Nothing &
families . each %~
\fam -> fam &
head1 %~ set (ix $ Just n) Nothing id &
head2 %~ set (ix $ Just n) Nothing id &
children . contains n .~ False
deleteFamily :: Int -> FamilyTree -> FamilyTree
deleteFamily n = families . at n .~ Nothing
newTree :: Text -> FamilyTree
newTree n = FamilyTree
{_treeName = n
,_people = empty
,_families = empty
}