module Types where
import Data.Functor
import Data.List
import Data.List.NonEmpty (NonEmpty)
import qualified Data.List.NonEmpty as NE

--                     Word   Description
data Card = Definition String String
          | OpenQuestion String Perforated
          | MultipleChoice {
            question   :: String,
            correct    :: CorrectOption,
            incorrects :: [IncorrectOption]}
          | MultipleAnswer {
            question   :: String,
            options    :: NonEmpty Option }
          | Reorder {
            question   :: String,
            elements   :: NonEmpty (Int, String)
          }

instance Show Card where
  show card = let showHeader h = "# " <> h <> "\n"
    in case card of
      Definition h descr -> showHeader h <> descr
      OpenQuestion h p -> showHeader h <> show p
      MultipleChoice h c inc ->
        showHeader h <> showMultipleChoice c inc
      MultipleAnswer h opts ->
        showHeader h <> unlines' (NE.toList (NE.map show opts))
      Reorder h elts ->
        showHeader h <> unlines' (NE.toList (NE.map showReorder elts))

data Type = Incorrect | Correct
  deriving (Show, Eq)
data CorrectOption = CorrectOption Int String
  deriving Show
newtype IncorrectOption = IncorrectOption String
  deriving Show
data Option = Option Type String
instance Show Option where
  show (Option Correct str)   = "[*] " <> str
  show (Option Incorrect str) = "[ ] " <> str

--                         Pre    Gap               Post
data Sentence = Perforated String (NonEmpty String) Sentence
              | Normal String

instance Show Sentence where
  show = foldSentence id (\pre gap sent -> pre <> "_" <> concat (NE.intersperse "|" gap) <> "_" <> sent)

data Perforated = P String (NonEmpty String) Sentence

instance Show Perforated where
  show = show . perforatedToSentence

listMultipleChoice :: CorrectOption -> [IncorrectOption] -> [String]
listMultipleChoice c = reverse . listMultipleChoice' [] 0 c
  where listMultipleChoice' opts i (CorrectOption j cStr) [] =
          if i == j
            then cStr : opts
            else opts
        listMultipleChoice' opts i c'@(CorrectOption j cStr) ics@(IncorrectOption icStr : ics') =
          if i == j
            then listMultipleChoice' (cStr  : opts) (i+1) c' ics
            else listMultipleChoice' (icStr : opts) (i+1) c' ics'

unlines' :: [String] -> String
unlines' = intercalate "\n"

showMultipleChoice :: CorrectOption -> [IncorrectOption] -> String
showMultipleChoice c@(CorrectOption i _) inc =
  unlines' . map showOne $ zip [0..] (listMultipleChoice c inc)
    where showOne (j, s) = (if i == j then "* " else "- ") <> s

showReorder :: (Int, String) -> String
showReorder (i, s) = show i <> ". " <> s

cardsToString :: [Card] -> String
cardsToString = unlines . intersperse "---" . map show

nGapsInSentence :: Sentence -> Int
nGapsInSentence = nGapsInSentence' 0
  where
    nGapsInSentence' acc (Normal _) = acc
    nGapsInSentence' acc (Perforated _ _ post) = nGapsInSentence' (1+acc) post

foldSentence :: (String -> a) -> (String -> NonEmpty String -> a -> a) -> Sentence -> a
foldSentence norm perf = f where
  f (Normal text) = norm text
  f (Perforated pre gap sent) = perf pre gap (f sent)

foldSentenceIndex :: (String -> Int -> a) -> (String -> NonEmpty String -> a -> Int -> a) -> Sentence -> a
foldSentenceIndex norm perf = f 0 where
  f i (Normal text) = norm text i
  f i (Perforated pre gap sent) = perf pre gap (f (i+1) sent) i

perforatedToSentence :: Perforated -> Sentence
perforatedToSentence (P pre gap sentence) = Perforated pre gap sentence

nGapsInPerforated :: Perforated -> Int
nGapsInPerforated = nGapsInSentence . perforatedToSentence

sentenceToGaps :: Sentence -> [NonEmpty String]
sentenceToGaps = foldSentence (const []) (\_ gap acc -> gap : acc)

isOptionCorrect :: Option -> Bool
isOptionCorrect (Option Correct _) = True
isOptionCorrect _                  = False