{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE DeriveFoldable #-}
{-# LANGUAGE DeriveTraversable #-}
{-# LANGUAGE OverloadedStrings #-}

module Descript.BasicInj.Data.Type
  ( Symbol (..)
  , RecordType (..)
  , RecordDecl (..)
  , RecordCtx (..)
  , recordCtxTypes
  , lookupRecordType
  , recTypeHasHead
  ) where

import Descript.BasicInj.Data.Atom
import Descript.Misc
import Data.Semigroup as S
import Data.Monoid as M
import Data.List hiding (head)
import Prelude hiding (head)

-- | A record type.
data RecordType an
  = RecordType
  { recordTypeAnn :: an
  , head :: FSymbol an -- ^ Identifies and distinguishes the type.
  , properties :: [Symbol an] -- ^ All instances should have properties with these keys.
  } deriving (Eq, Ord, Read, Show, Functor, Foldable, Traversable)

-- | A record declaration.
data RecordDecl an
  = RecordDecl
  { recordDeclAnn :: an
  , recordDeclType :: RecordType an
  } deriving (Eq, Ord, Read, Show, Functor, Foldable, Traversable)

-- | Contains a source file's data definitions.
-- These record types encode the types of records which can be used
-- throughout the rest of the source.
-- Each of them should have a different head.
data RecordCtx an
  = RecordCtx
  { recordCtxAnn :: an
  , recordCtxDecls :: [RecordDecl an]
  } deriving (Eq, Ord, Read, Show, Functor, Foldable, Traversable)

instance (Semigroup an) => Semigroup (RecordCtx an) where
  RecordCtx xAnn xds <> RecordCtx yAnn yds
    = RecordCtx (xAnn S.<> yAnn) (xds ++ yds)

instance (Monoid an) => Monoid (RecordCtx an) where
  mempty = RecordCtx mempty []
  RecordCtx xAnn xds `mappend` RecordCtx yAnn yds
    = RecordCtx (xAnn M.<> yAnn) (xds ++ yds)

instance Ann RecordCtx where
  getAnn = recordCtxAnn

instance Ann RecordDecl where
  getAnn = recordDeclAnn

instance Ann RecordType where
  getAnn = recordTypeAnn

instance Printable RecordCtx where
  aprintRec sub (RecordCtx _ recordDecls) = pintercal "\n" $ map sub recordDecls

instance Printable RecordDecl where
  aprintRec sub (RecordDecl _ recordType) = sub recordType M.<> "."

instance Printable RecordType where
  aprintRec sub recordType = sub (head recordType) M.<> propsPrinted
    where propsPrinted = "[" M.<> pintercal ", " propPrinteds M.<> "]"
          propPrinteds = map sub $ properties recordType

instance (Show an) => Summary (RecordCtx an) where
  summaryRec = pprintSummaryRec

instance (Show an) => Summary (RecordDecl an) where
  summaryRec = pprintSummaryRec

instance (Show an) => Summary (RecordType an) where
  summaryRec = pprintSummaryRec

-- | The record types declared in the context.
recordCtxTypes :: RecordCtx an -> [RecordType an]
recordCtxTypes = map recordDeclType . recordCtxDecls

-- | Finds the record type with the given head in the context.
lookupRecordType :: FSymbol an -> RecordCtx an -> Maybe (RecordType an)
lookupRecordType head' = find (recTypeHasHead head') . recordCtxTypes

-- | Does the record type have the given head?
recTypeHasHead :: FSymbol an1 -> RecordType an2 -> Bool
recTypeHasHead head' record = head' =@= head record