PLFA agda exercises
module Buildfile.Book where

import Control.Monad ((<=<))
import Data.Aeson.Types (FromJSON (..), ToJSON (..), object, withObject, (.!=), (.:), (.:?), (.=))
import Data.Bimap qualified as Bimap
import Data.Text (Text)

newtype Book = Book
  { bookParts :: [Part]
  }
  deriving (Show)

instance FromJSON Book where
  parseJSON = withObject "Book" $ \v ->
    Book
      <$> v .: "part"

instance ToJSON Book where
  toJSON Book {..} =
    object
      [ "part" .= bookParts
      ]

data Part = Part
  { partTitle :: Text,
    partChapters :: [Chapter],
    partFrontmatter :: Bool,
    partMainmatter :: Bool,
    partBackmatter :: Bool
  }
  deriving (Show)

instance FromJSON Part where
  parseJSON = withObject "Part" $ \v ->
    Part
      <$> v .: "title"
      <*> v .: "chapter"
      <*> v .:? "frontmatter" .!= False
      <*> v .:? "mainmatter" .!= False
      <*> v .:? "backmatter" .!= False

instance ToJSON Part where
  toJSON Part {..} =
    object
      [ "title" .= partTitle,
        "chapter" .= partChapters,
        "frontmatter" .= partFrontmatter,
        "mainmatter" .= partMainmatter,
        "backmatter" .= partBackmatter
      ]

data Chapter = Chapter
  { chapterInclude :: FilePath,
    chapterEpubType :: Text
  }
  deriving (Show)

instance FromJSON Chapter where
  parseJSON = withObject "Chapter" $ \v ->
    Chapter
      <$> v .: "include"
      <*> v .:? "epub-type" .!= "bodymatter"

instance ToJSON Chapter where
  toJSON Chapter {..} =
    object
      [ "include" .= chapterInclude,
        "epub-type" .= chapterEpubType
      ]

newtype ChapterTable = ChapterTable {chapterBimap :: Bimap.Bimap FilePath FilePath}

fromBook :: Book -> ChapterTable
fromBook book = ChapterTable {..}
  where
    chapterBimap :: Bimap.Bimap FilePath FilePath
    chapterBimap = Bimap.fromList $ zip chapterList (tail chapterList)

    chapterList :: [FilePath]
    chapterList = flattenBook book
      where
        flattenBook :: Book -> [FilePath]
        flattenBook = flattenPart <=< bookParts

        flattenPart :: Part -> [FilePath]
        flattenPart = flattenChapter <=< partChapters

        flattenChapter :: Chapter -> [FilePath]
        flattenChapter = return . chapterInclude

nextChapter :: ChapterTable -> FilePath -> Maybe FilePath
nextChapter ChapterTable {..} src = Bimap.lookup src chapterBimap

previousChapter :: ChapterTable -> FilePath -> Maybe FilePath
previousChapter ChapterTable {..} src = Bimap.lookupR src chapterBimap