{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE NamedFieldPuns        #-}
{-# LANGUAGE OverloadedStrings     #-}
module Trans.FlatToRaw (cgrToFiles) where

import qualified IR.Common as C
import qualified IR.Flat   as Flat
import qualified IR.Name   as Name
import qualified IR.Raw    as Raw

cgrToFiles :: Flat.CodeGenReq -> [Raw.File]
cgrToFiles Flat.CodeGenReq{reqFiles} = map fileToFile reqFiles

fileToFile :: Flat.File -> Raw.File
fileToFile Flat.File{nodes, fileId, fileName} =
    Raw.File
        { fileName
        , fileId
        , decls = concatMap nodeToDecls nodes
        }

nodeToDecls :: Flat.Node -> [Raw.Decl]
nodeToDecls Flat.Node{name=Name.CapnpQ{fileId, local}, union_} = case union_ of
    Flat.Enum variants ->
        [ Raw.Enum
            { typeCtor = local
            , dataCtors = map (Name.mkSub local) variants
            }
        ]
    Flat.Struct{fields, isGroup, dataWordCount, pointerCount, union} ->
        concat
            [ [ Raw.StructWrapper { typeCtor = local } ]
            , if isGroup
                then
                    []
                else
                    [ Raw.StructInstances
                        { typeCtor = local
                        , dataWordCount
                        , pointerCount
                        }
                    ]
            , concatMap (fieldToDecls local) fields
            , case union of
                Nothing -> []
                Just Flat.Union{variants, tagOffset} ->
                    let local' = Name.mkSub local "" in
                    [ Raw.UnionVariant
                        { parentTypeCtor = local
                        , tagOffset
                        , unionDataCtors =
                            [ Raw.Variant
                                { name = local
                                , tagValue
                                , locType = fmap (\Flat.Node{name} -> name) fieldLocType
                                }
                            | Flat.Variant
                                { field = Flat.Field
                                    { fieldName = Name.CapnpQ{local}
                                    , fieldLocType
                                    }
                                , tagValue
                                } <- variants
                            ]
                        }
                    , Raw.Getter
                        { fieldName = local'
                        , containerType = local
                        , fieldLocType = C.HereField $ C.StructType Name.CapnpQ
                            { fileId
                            , local = local'
                            }
                        }
                    ] ++
                    [ Raw.Setter
                        { fieldName
                        , containerType = local
                        , fieldLocType = fmap (\Flat.Node{name} -> name) fieldLocType
                        , tag = Just Raw.TagSetter
                            { tagOffset
                            , tagValue
                            }
                        }
                    | Flat.Variant
                        { field = Flat.Field
                            { fieldName = Name.CapnpQ { local = fieldName }
                            , fieldLocType
                            }
                        , tagValue
                        } <- variants
                    ] ++
                    -- This is kindof another tag setter, but it has to work a bit
                    -- differently for the unknown' variant, because it takes an
                    -- argument for what the tag should be, but also doesn't need to
                    -- set any other values. We can treat is as just a setter for a
                    -- uint16 field in the same spot:
                    [ Raw.Setter
                        { fieldName = Name.mkSub local "unknown'"
                        , containerType = local
                        , fieldLocType = C.DataField
                            (Raw.tagOffsetToDataLoc tagOffset)
                            (C.PrimWord $ C.PrimInt $ C.IntType C.Unsigned C.Sz16)
                        , tag = Nothing
                        }
                    ]
        ]
    Flat.Interface{} ->
        [ Raw.InterfaceWrapper
            { typeCtor = local
            }
        ]
    Flat.Constant{ value } ->
        [ Raw.Constant
            { name = local
            , value = fmap (\Flat.Node{name} -> name) value
            }
        ]
    Flat.Other -> []

fieldToDecls :: Name.LocalQ -> Flat.Field -> [Raw.Decl]
fieldToDecls containerType Flat.Field{fieldName=Name.CapnpQ{local=fieldName}, fieldLocType} =
    [ Raw.Getter
        { fieldName
        , containerType
        , fieldLocType = fmap (\Flat.Node{name} -> name) fieldLocType
        }
    ]
    ++
    case fieldLocType of
         C.HereField _ -> []
         _ ->
            [ Raw.Setter
                { fieldName
                , containerType
                , fieldLocType = fmap (\Flat.Node{name} -> name) fieldLocType
                , tag = Nothing
                }
            ]
    ++
    case fieldLocType of
        C.PtrField ptrIndex _ ->
            [ Raw.HasFn
                { fieldName
                , containerType
                , ptrIndex
                }
            ]
        _ ->
            []
    ++
    case fieldLocType of
        C.PtrField _ (C.ListOf _) ->
            [ Raw.NewFn
                { fieldName
                , containerType
                , fieldLocType = fmap (\Flat.Node{name} -> name) fieldLocType
                , newFnType = Raw.NewList
                }
            ]
        C.PtrField _ (C.PrimPtr C.PrimText) ->
            [ Raw.NewFn
                { fieldName
                , containerType
                , fieldLocType = fmap (\Flat.Node{name} -> name) fieldLocType
                , newFnType = Raw.NewText
                }
            ]
        C.PtrField _ (C.PrimPtr C.PrimData) ->
            [ Raw.NewFn
                { fieldName
                , containerType
                , fieldLocType = fmap (\Flat.Node{name} -> name) fieldLocType
                , newFnType = Raw.NewData
                }
            ]
        C.PtrField _ (C.PtrComposite _) ->
            [ Raw.NewFn
                { fieldName
                , containerType
                , fieldLocType = fmap (\Flat.Node{name} -> name) fieldLocType
                , newFnType = Raw.NewStruct
                }
            ]
        _ ->
            []
