{-# LANGUAGE DefaultSignatures #-}
{-# LANGUAGE FlexibleContexts  #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeOperators     #-}

module Elm.Decoder
  ( toElmDecoderRef
  , toElmDecoderRefWith
  , toElmDecoderSource
  , toElmDecoderSourceWith
  ) where

import           Control.Monad.Reader
import           Data.Text
import           Elm.Common
import           Elm.Type
import           Formatting

class HasDecoder a where
  render :: a -> Reader Options Text

class HasDecoderRef a where
  renderRef :: a -> Reader Options Text

instance HasDecoder ElmDatatype where
    render d@(ElmDatatype name constructor) = do
        fnName <- renderRef d
        sformat
            (stext % " : Decoder " % stext % cr % stext % " =" % cr % stext)
            fnName
            name
            fnName <$>
            render constructor
    render (ElmPrimitive primitive) = renderRef primitive

instance HasDecoderRef ElmDatatype where
    renderRef (ElmDatatype name _) =
        pure $ sformat ("decode" % stext) name

    renderRef (ElmPrimitive primitive) =
        renderRef primitive


instance HasDecoder ElmConstructor where
    render (NamedConstructor name value) =
        sformat ("    decode " % stext % cr % stext) name <$> render value
    render (RecordConstructor name value) =
        sformat ("    decode " % stext % cr % stext) name <$> render value


instance HasDecoder ElmValue where
    render (ElmRef name) = pure (sformat ("decode" % stext) name)
    render (ElmPrimitiveRef primitive) = renderRef primitive
    render (Values x y) = sformat (stext % cr % stext) <$> render x <*> render y
    render (ElmField name value) = do
        fieldModifier <- asks fieldLabelModifier
        sformat
            ("        |> required \"" % stext % "\" " % stext)
            (fieldModifier name) <$>
            render value


instance HasDecoderRef ElmPrimitive where
    renderRef (EList (ElmPrimitive EChar)) = pure "string"
    renderRef (EList datatype) =
        sformat ("(list " % stext % ")") <$> renderRef datatype
    renderRef (EDict key value) =
        sformat ("(map Dict.fromList " % stext % ")") <$>
        renderRef (EList (ElmPrimitive (ETuple2 (ElmPrimitive key) value)))
    renderRef (EMaybe datatype) =
        sformat ("(maybe " % stext % ")") <$> renderRef datatype
    renderRef (ETuple2 x y) =
        sformat ("(tuple2 (,) " % stext % " " % stext % ")") <$> renderRef x <*>
        renderRef y
    renderRef EUnit = pure "(succeed ())"
    renderRef EDate = pure "(customDecoder string Date.fromString)"
    renderRef EInt = pure "int"
    renderRef EBool = pure "bool"
    renderRef EChar = pure "char"
    renderRef EFloat = pure "float"
    renderRef EString = pure "string"


toElmDecoderRefWith :: ElmType a => Options -> a -> Text
toElmDecoderRefWith options x = runReader (renderRef (toElmType x)) options


toElmDecoderRef :: ElmType a => a -> Text
toElmDecoderRef = toElmDecoderRefWith defaultOptions


toElmDecoderSourceWith :: ElmType a => Options -> a -> Text
toElmDecoderSourceWith options x = runReader (render (toElmType x)) options


toElmDecoderSource :: ElmType a => a -> Text
toElmDecoderSource = toElmDecoderSourceWith defaultOptions