module Writer exposing (write, writeDeclaration, writeExpression, writeFile, writePattern, writeTypeAnnotation)

{-| Copied and tweaked from Elm Syntax.


# Elm.Writer

Write a file to a string.
@docs write, writeFile, writePattern, writeExpression, writeTypeAnnotation, writeDeclaration

-}

import Elm.Syntax.Declaration exposing (..)
import Elm.Syntax.Documentation exposing (..)
import Elm.Syntax.Exposing as Exposing exposing (..)
import Elm.Syntax.Expression exposing (..)
import Elm.Syntax.File exposing (..)
import Elm.Syntax.Import exposing (Import)
import Elm.Syntax.Infix exposing (..)
import Elm.Syntax.Module exposing (..)
import Elm.Syntax.ModuleName exposing (..)
import Elm.Syntax.Node as Node exposing (Node(..))
import Elm.Syntax.Pattern exposing (..)
import Elm.Syntax.Range exposing (Range)
import Elm.Syntax.Signature exposing (Signature)
import Elm.Syntax.Type exposing (..)
import Elm.Syntax.TypeAlias exposing (..)
import Elm.Syntax.TypeAnnotation exposing (..)
import List.Extra as List
import StructuredWriter as Writer exposing (..)


{-| Transform a writer to a string
-}
write : Writer -> String
write =
    Writer.write


{-| Write a file
-}
writeFile : File -> Writer
writeFile file =
    breaked
        [ writeModule <| Node.value file.moduleDefinition
        , breaked (List.map (Node.value >> writeImport) file.imports)
        , breaked (List.map writeDeclaration file.declarations)
        ]


writeModule : Module -> Writer
writeModule m =
    case m of
        NormalModule defaultModuleData ->
            writeDefaultModuleData defaultModuleData

        PortModule defaultModuleData ->
            spaced
                [ string "port"
                , writeDefaultModuleData defaultModuleData
                ]

        EffectModule effectModuleData ->
            writeEffectModuleData effectModuleData


writeDefaultModuleData : DefaultModuleData -> Writer
writeDefaultModuleData { moduleName, exposingList } =
    spaced
        [ string "module"
        , writeModuleName <| Node.value moduleName
        , writeExposureExpose <| Node.value exposingList
        ]


writeEffectModuleData : EffectModuleData -> Writer
writeEffectModuleData { moduleName, exposingList, command, subscription } =
    spaced
        [ string "effect"
        , string "module"
        , writeModuleName <| Node.value moduleName
        , writeWhere ( command, subscription )
        , writeExposureExpose <| Node.value exposingList
        ]


writeWhere : ( Maybe (Node String), Maybe (Node String) ) -> Writer
writeWhere input =
    case input of
        ( Nothing, Nothing ) ->
            epsilon

        ( Just x, Nothing ) ->
            spaced
                [ string "where { command ="
                , string <| Node.value x
                , string "}"
                ]

        ( Nothing, Just x ) ->
            spaced
                [ string "where { subscription ="
                , string <| Node.value x
                , string "}"
                ]

        ( Just x, Just y ) ->
            spaced
                [ string "where { command ="
                , string <| Node.value x
                , string ", subscription ="
                , string <| Node.value y
                , string "}"
                ]


writeModuleName : ModuleName -> Writer
writeModuleName moduleName =
    string (String.join "." moduleName)


writeExposureExpose : Exposing -> Writer
writeExposureExpose x =
    case x of
        All _ ->
            string "exposing (..)"

        Explicit exposeList ->
            let
                diffLines =
                    List.map Node.range exposeList
                        |> startOnDifferentLines
            in
            spaced
                [ string "exposing"
                , parensComma diffLines (List.map writeExpose exposeList)
                ]


writeExpose : Node TopLevelExpose -> Writer
writeExpose (Node _ exp) =
    case exp of
        InfixExpose x ->
            string ("(" ++ x ++ ")")

        FunctionExpose f ->
            string f

        TypeOrAliasExpose t ->
            string t

        TypeExpose { name, open } ->
            case open of
                Just _ ->
                    spaced
                        [ string name
                        , string "(..)"
                        ]

                Nothing ->
                    string name


startOnDifferentLines : List Range -> Bool
startOnDifferentLines xs =
    List.length (List.unique (List.map (.start >> .row) xs)) > 1


writeImport : Import -> Writer
writeImport { moduleName, moduleAlias, exposingList } =
    spaced
        [ string "import"
        , writeModuleName <| Node.value moduleName
        , maybe (Maybe.map (Node.value >> writeModuleName >> (\x -> spaced [ string "as", x ])) moduleAlias)
        , maybe (Maybe.map writeExposureExpose exposingList)
        ]


writeLetDeclaration : Node LetDeclaration -> Writer
writeLetDeclaration (Node _ letDeclaration) =
    case letDeclaration of
        LetFunction function ->
            writeFunction function

        LetDestructuring pattern expression ->
            writeDestructuring pattern expression


{-| Write a declaration
-}
writeDeclaration : Node Declaration -> Writer
writeDeclaration (Node _ decl) =
    case decl of
        FunctionDeclaration function ->
            writeFunction function

        AliasDeclaration typeAlias ->
            writeTypeAlias typeAlias

        CustomTypeDeclaration type_ ->
            writeType type_

        PortDeclaration p ->
            writePortDeclaration p

        InfixDeclaration i ->
            writeInfix i

        Destructuring pattern expression ->
            writeDestructuring pattern expression


writeFunction : Function -> Writer
writeFunction { documentation, signature, declaration } =
    breaked
        [ maybe (Maybe.map writeDocumentation documentation)
        , maybe (Maybe.map (Node.value >> writeSignature) signature)
        , writeFunctionImplementation <| Node.value declaration
        ]


writeFunctionImplementation : FunctionImplementation -> Writer
writeFunctionImplementation declaration =
    breaked
        [ spaced
            [ string <| Node.value declaration.name
            , spaced (List.map writePattern declaration.arguments)
            , string "="
            ]
        , indent 4 (writeExpression declaration.expression)
        ]


writeSignature : Signature -> Writer
writeSignature signature =
    spaced
        [ string <| Node.value signature.name
        , string ":"
        , writeTypeAnnotation signature.typeAnnotation
        ]


writeDocumentation : Node Documentation -> Writer
writeDocumentation =
    Node.value >> string


writeTypeAlias : TypeAlias -> Writer
writeTypeAlias typeAlias =
    breaked
        [ spaced
            [ string "type alias"
            , string <| Node.value typeAlias.name
            , spaced (List.map (Node.value >> string) typeAlias.generics)
            , string "="
            ]
        , indent 4 (writeTypeAnnotation typeAlias.typeAnnotation)
        ]


writeType : Type -> Writer
writeType type_ =
    breaked
        [ spaced
            [ string "type"
            , string <| Node.value type_.name
            , spaced (List.map (Node.value >> string) type_.generics)
            ]
        , let
            diffLines =
                List.map Node.range type_.constructors
                    |> startOnDifferentLines
          in
          sepBy ( "=", "|", "" )
            diffLines
            (List.map (Node.value >> writeValueConstructor) type_.constructors)
        ]


writeValueConstructor : ValueConstructor -> Writer
writeValueConstructor { name, arguments } =
    spaced
        [ string <| Node.value name
        , spaced (List.map writeTypeAnnotation arguments)
        ]


writePortDeclaration : Signature -> Writer
writePortDeclaration signature =
    spaced [ string "port", writeSignature signature ]


writeInfix : Infix -> Writer
writeInfix { direction, precedence, operator, function } =
    spaced
        [ string "infix"
        , case Node.value direction of
            Left ->
                string "left"

            Right ->
                string "right"

            Non ->
                string "non"
        , string (String.fromInt (Node.value precedence))
        , string (Node.value operator)
        , string "="
        , string (Node.value function)
        ]


writeDestructuring : Node Pattern -> Node Expression -> Writer
writeDestructuring pattern expression =
    breaked
        [ spaced [ writePattern pattern, string "=" ]
        , indent 4 (writeExpression expression)
        ]


{-| Write a type annotation
-}
writeTypeAnnotation : Node TypeAnnotation -> Writer
writeTypeAnnotation (Node _ typeAnnotation) =
    case typeAnnotation of
        GenericType s ->
            string s

        Typed moduleNameAndName args ->
            let
                moduleName =
                    Node.value moduleNameAndName |> Tuple.first

                k =
                    Node.value moduleNameAndName |> Tuple.second
            in
            spaced
                ((string <| String.join "." (moduleName ++ [ k ]))
                    :: List.map (writeTypeAnnotation >> parensIfContainsSpaces) args
                )

        Unit ->
            string "()"

        Tupled xs ->
            parensComma False (List.map writeTypeAnnotation xs)

        Record xs ->
            bracesComma False (List.map writeRecordField xs)

        GenericRecord name fields ->
            spaced
                [ string "{"
                , string <| Node.value name
                , string "|"
                , sepByComma False (List.map writeRecordField <| Node.value fields)
                , string "}"
                ]

        FunctionTypeAnnotation left right ->
            let
                addParensForSubTypeAnnotation type_ =
                    case type_ of
                        Node _ (FunctionTypeAnnotation _ _) ->
                            join [ string "(", writeTypeAnnotation type_, string ")" ]

                        _ ->
                            writeTypeAnnotation type_
            in
            spaced
                [ addParensForSubTypeAnnotation left
                , string "->"
                , addParensForSubTypeAnnotation right
                ]


writeRecordField : Node RecordField -> Writer
writeRecordField (Node _ ( name, ref )) =
    spaced
        [ string <| Node.value name
        , string ":"
        , writeTypeAnnotation ref
        ]


{-| Writer an expression
-}
writeExpression : Node Expression -> Writer
writeExpression (Node range inner) =
    let
        recurRangeHelper (Node x y) =
            ( x, writeExpression (Node x y) )

        writeRecordSetter : RecordSetter -> ( Range, Writer )
        writeRecordSetter ( name, expr ) =
            ( Node.range expr
            , spaced [ string <| Node.value name, string "=", writeExpression expr ]
            )

        sepHelper : (Bool -> List Writer -> Writer) -> List ( Range, Writer ) -> Writer
        sepHelper f l =
            let
                diffLines =
                    List.map Tuple.first l
                        |> startOnDifferentLines
            in
            f diffLines (List.map Tuple.second l)

        fakeSepHelper : (Bool -> List Writer -> Writer) -> List ( Range, Writer ) -> Writer
        fakeSepHelper f l =
            f True (List.map Tuple.second l)
    in
    case inner of
        UnitExpr ->
            string "()"

        Application xs ->
            case xs of
                [] ->
                    epsilon

                [ x ] ->
                    writeExpression x

                x :: rest ->
                    spaced
                        [ writeExpression x
                        , sepHelper sepBySpace (List.map recurRangeHelper rest)
                        ]

        OperatorApplication x dir left right ->
            case dir of
                Left ->
                    sepHelper sepBySpace
                        [ ( Node.range left, writeExpression left )
                        , ( range, spaced [ string x, writeExpression right ] )
                        ]

                Right ->
                    sepHelper sepBySpace
                        [ ( Node.range left, spaced [ writeExpression left, string x ] )
                        , ( Node.range right, writeExpression right )
                        ]

                Non ->
                    sepHelper sepBySpace
                        [ ( Node.range left, spaced [ writeExpression left, string x ] )
                        , ( Node.range right, writeExpression right )
                        ]

        FunctionOrValue moduleName name ->
            case moduleName of
                [] ->
                    string name

                _ ->
                    join
                        [ writeModuleName <| moduleName
                        , string "."
                        , string <| name
                        ]

        IfBlock condition thenCase elseCase ->
            breaked
                [ spaced [ string "if", writeExpression condition, string "then" ]
                , indent 2 (writeExpression thenCase)
                , string "else"
                , indent 2 (writeExpression elseCase)
                ]

        PrefixOperator x ->
            string ("(" ++ x ++ ")")

        Operator x ->
            string x

        Hex h ->
            string "TODO"

        Integer i ->
            string (String.fromInt i)

        Floatable f ->
            string (String.fromFloat f)

        Negation x ->
            append (string "-") (writeExpression x)

        Literal s ->
            string ("\"" ++ s ++ "\"")

        CharLiteral c ->
            string ("'" ++ String.fromList [ c ] ++ "'")

        TupledExpression t ->
            sepHelper sepByComma (List.map recurRangeHelper t)

        ParenthesizedExpression x ->
            join [ string "(", writeExpression x, string ")" ]

        LetExpression letBlock ->
            breaked
                [ string "let"
                , indent 2 (breaked (List.map writeLetDeclaration letBlock.declarations))
                , string "in"
                , indent 2 (writeExpression letBlock.expression)
                ]

        CaseExpression caseBlock ->
            let
                writeCaseBranch ( pattern, expression ) =
                    indent 2 <|
                        breaked
                            [ spaced [ writePattern pattern, string "->" ]
                            , indent 2 (writeExpression expression)
                            ]
            in
            breaked
                [ spaced [ string "case", writeExpression caseBlock.expression, string "of" ]
                , breaked (List.map writeCaseBranch caseBlock.cases)
                ]

        LambdaExpression lambda ->
            spaced
                [ join
                    [ string "\\"
                    , spaced (List.map writePattern lambda.args)
                    ]
                , string "->"
                , writeExpression lambda.expression
                ]

        RecordExpr setters ->
            --sepHelper bracesComma (List.map (Node.value >> writeRecordSetter) setters)
            bracesComma True (List.map (Node.value >> (\( name, expr ) -> spaced [ string <| Node.value name, string "=", writeExpression expr ])) setters)

        ListExpr xs ->
            fakeSepHelper bracketsComma (List.map recurRangeHelper xs)

        RecordAccess expression accessor ->
            join [ writeExpression expression, string ".", string <| Node.value accessor ]

        RecordAccessFunction s ->
            join [ string ".", string s ]

        RecordUpdateExpression name updates ->
            spaced
                [ string "{"
                , string <| Node.value name
                , string "|"
                , sepHelper sepByComma (List.map (Node.value >> writeRecordSetter) updates)
                , string "}"
                ]

        GLSLExpression s ->
            join
                [ string "[glsl|"
                , string s
                , string "|]"
                ]


{-| Write a pattern
-}
writePattern : Node Pattern -> Writer
writePattern (Node _ p) =
    case p of
        AllPattern ->
            string "_"

        UnitPattern ->
            string "()"

        CharPattern c ->
            string ("'" ++ String.fromList [ c ] ++ "'")

        StringPattern s ->
            string s

        HexPattern h ->
            string "TODO"

        IntPattern i ->
            string (String.fromInt i)

        FloatPattern f ->
            string (String.fromFloat f)

        TuplePattern inner ->
            parensComma False (List.map writePattern inner)

        RecordPattern inner ->
            bracesComma False (List.map (Node.value >> string) inner)

        UnConsPattern left right ->
            spaced [ writePattern left, string "::", writePattern right ]

        ListPattern inner ->
            bracketsComma False (List.map writePattern inner)

        VarPattern var ->
            string var

        NamedPattern qnr others ->
            spaced
                [ writeQualifiedNameRef qnr
                , spaced (List.map writePattern others)
                ]

        AsPattern innerPattern asName ->
            spaced [ writePattern innerPattern, string "as", string <| Node.value asName ]

        ParenthesizedPattern innerPattern ->
            spaced [ string "(", writePattern innerPattern, string ")" ]


writeQualifiedNameRef : QualifiedNameRef -> Writer
writeQualifiedNameRef { moduleName, name } =
    case moduleName of
        [] ->
            string name

        _ ->
            join
                [ writeModuleName moduleName
                , string "."
                , string name
                ]



-- Helpers


parensIfContainsSpaces : Writer -> Writer
parensIfContainsSpaces w =
    if Writer.write w |> String.contains " " then
        join [ string "(", w, string ")" ]
    else
        w