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

import Data.Word

import qualified Data.Map.Strict as M

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

type IFaceMap = M.Map Word64 Pure.Interface

cgrToFiles :: Flat.CodeGenReq -> [Pure.File]
cgrToFiles Flat.CodeGenReq{allNodes, reqFiles} =
    map oneFile reqFiles
  where
    allInterfaces = concatMap (convertInterface ifaceMap) allNodes
    ifaceMap = M.fromList
        [ (interfaceId, iface)
        | iface@Pure.IFace{interfaceId} <- allInterfaces
        ]
    oneFile Flat.File{nodes, fileId, fileName} =
        Pure.File
            { fileId
            , fileName
            , decls = concatMap (nodeToDecls ifaceMap) nodes
            , reExportEnums = concatMap nodeToReExports nodes
            , usesRpc = not $ null [ () | Flat.Node{ union_ = Flat.Interface{} } <- nodes ]
            }

convertInterface :: IFaceMap -> Flat.Node -> [Pure.Interface]
convertInterface
    ifaceMap
    Flat.Node
        { name
        , nodeId
        , union_ = Flat.Interface{ methods, supers, ancestors }
        }
    =
    [ Pure.IFace
        { name
        , interfaceId = nodeId
        , methods = [ Pure.Method{..} | Flat.Method{..} <- methods ]
        , supers = [ ifaceMap M.! nodeId | Flat.Node{nodeId} <- supers ]
        , ancestors = [ ifaceMap M.! nodeId | Flat.Node{nodeId} <- ancestors ]
        }
    ]
convertInterface _ _ = []

nodeToReExports :: Flat.Node -> [Name.LocalQ]
nodeToReExports Flat.Node{name=Name.CapnpQ{local}, union_=Flat.Enum _} = [ local ]
nodeToReExports _ = []

unionToDecl :: Bool -> Name.LocalQ -> Name.LocalQ -> [Flat.Variant] -> Pure.Decl
unionToDecl firstClass cerialName local variants =
    Pure.DataDecl Pure.Data
        { typeName = local
        , cerialName
        , def = Pure.Sum
            [ Pure.Variant
                { name = variantName
                , arg = case fieldLocType of
                    C.VoidField ->
                        -- If the argument is void, just have no argument.
                        Nothing
                    _ ->
                        Just (Pure.type_ (fieldToField field))
                }
            | Flat.Variant
                { field=field@Flat.Field
                    { fieldName=Name.CapnpQ{local=variantName}
                    , fieldLocType
                    }
                } <- variants
            ]
        , firstClass
        }

nodeToDecls :: IFaceMap -> Flat.Node -> [Pure.Decl]
nodeToDecls ifaceMap Flat.Node{name=name@Name.CapnpQ{local}, nodeId, union_} = case union_ of
    Flat.Enum _ ->
        -- Don't need to do anything here, since we're just re-exporting the
        -- stuff from the raw module.
        []
    Flat.Interface{} ->
        [ Pure.IFaceDecl (ifaceMap M.! nodeId) ]
    Flat.Struct{ isGroup, fields=[], union=Just Flat.Union{variants}} ->
        -- It's just one big union; skip the outer struct wrapper and make it
        -- a top-level sum type.
        [ unionToDecl (not isGroup) local local variants ]
    -- Flat.Struct{ isGroup=True, union=Nothing } -> [] -- See Note [Collapsing Groups]
    Flat.Struct{ isGroup, fields, union } ->
        Pure.DataDecl Pure.Data
            { typeName = local
            , cerialName = local
            , def = Pure.Product $
                map fieldToField fields
                ++ case union of
                    Nothing ->
                        []
                    Just _ ->
                        [ Pure.Field
                            { name = "union'"
                            , type_ = C.CompositeType $ C.StructType $ Name.mkSub name ""
                            }
                        ]
            , firstClass = not isGroup
            }
        : case union of
            Just Flat.Union{variants} ->
                -- Also make a type that's just the union, but give it the
                -- same cerialName:
                [ unionToDecl False local (Name.mkSub local "") variants ]
            Nothing ->
                []
    Flat.Constant { value } ->
        [ Pure.ConstDecl Pure.Constant
            { name = local
            , value = fmap (\Flat.Node{name} -> name) value
            }
        ]
    Flat.Other -> []

fieldToField :: Flat.Field -> Pure.Field
fieldToField Flat.Field{fieldName, fieldLocType} = Pure.Field
    { name = Name.getUnQ fieldName
    , type_ = fmap
        (\Flat.Node{name} -> name)
        (C.fieldType fieldLocType)
    }

-- Note [Collapsing Groups]
-- ========================
--
-- If the argument to a union data constructor is a group, then the fields
-- never exist on their own, so it makes for a nicer API to just collapse
-- the fields directly into the variant, rather than creating an auxiliary
-- struct type.
--
-- However, this is only safe to do if the group does not itself have an
-- anonymous union. The reason for this is that otherwise we could end up
-- with two variants with a field "union'" but different types, which the
-- compiler will reject.
--
-- So the rule is, if a Field.Struct node is a group, and it does not itself
-- have an anonymous union:
--
-- 1. Don't generate a type for it.
-- 2. Collapse its fields into the data constructor for the union.
--
-- Note that we actually depend on (1) to avoid name collisions, since
-- otherwise both the data constructor for the anonymous union and the
-- data constructor for the group will be the same.
