-- Copyright (c) Microsoft. All rights reserved.

-- Licensed under the MIT license. See LICENSE file in the project root for full license information.



{-# LANGUAGE QuasiQuotes, OverloadedStrings, RecordWildCards #-}



module Language.Bond.Codegen.Cpp.Types_h (types_h) where



import System.FilePath

import Data.Maybe

import Data.Monoid

import Prelude

import Numeric

import Data.Text.Lazy.Builder

import qualified Data.Text.Lazy as L

import qualified Data.Foldable as F

import Text.Shakespeare.Text

import Paths_bond (version)

import Data.Version

import Language.Bond.Syntax.Types

import Language.Bond.Syntax.Util

import Language.Bond.Syntax.Internal

import Language.Bond.Util

import Language.Bond.Codegen.TypeMapping

import Language.Bond.Codegen.Util

import qualified Language.Bond.Codegen.Cpp.Util as CPP



-- | Codegen template for generating /base_name/_type.h containing definitions

-- of C++ types representing the schema.

types_h :: Maybe String -- ^ Optional attribute to decorate the enum conversion function declarations

        -> [String]     -- ^ list of optional header files to be @#include@'ed by the generated code

        -> Bool         -- ^ 'True' to generate enum definitions into a separate file /base_name/_enum.h

        -> Maybe String -- ^ optional custom allocator to be used in the generated code

        -> Bool         -- ^ 'True' to generate constructors with allocator

        -> Bool         -- ^ 'True' to generate type aliases

        -> Bool         -- ^ 'True' to use std::scoped_allocator_adaptor for strings and containers

        -> MappingContext -> String -> [Import] -> [Declaration] -> (String, L.Text)

types_h export_attribute userHeaders enumHeader allocator alloc_ctors_enabled type_aliases_enabled scoped_alloc_enabled cpp file imports declarations = ("_types.h", [lt|

#pragma once

#{newlineBeginSep 0 includeHeader userHeaders}

#include <bond/core/bond_version.h>



#if BOND_VERSION < 0x0800

#error This file was generated by a newer version of the Bond compiler and is incompatible with your version of the Bond library.

#endif



#if BOND_MIN_CODEGEN_VERSION > 0x#{hexVersion version}

#error This file was generated by an older version of the Bond compiler and is incompatible with your version of the Bond library.

#endif



#include <bond/core/config.h>

#include <bond/core/containers.h>

#{newlineSep 0 optionalHeader bondHeaders}

#{includeEnum}

#{newlineSepEnd 0 includeImport imports}

#{CPP.openNamespace cpp}

    #{doubleLineSepEnd 1 id $ catMaybes $ aliasDeclarations}#{doubleLineSep 1 typeDeclaration declarations}

#{CPP.closeNamespace cpp}

|])

  where

    aliasDeclarations = if type_aliases_enabled then map aliasDeclName declarations else []



    aliasDeclName a@Alias {..} = Just [lt|#{CPP.template a}using #{declName} = #{getAliasDeclTypeName cpp a};|]

    aliasDeclName _ = Nothing



    hexVersion (Version xs _) = foldr showHex "" xs

    cppType = getTypeName cpp



    cppExpandAliases = if type_aliases_enabled then cpp { typeMapping = cppExpandAliasesTypeMapping $ typeMapping cpp } else cpp

    cppTypeExpandAliases = getTypeName cppExpandAliases



    idl = MappingContext idlTypeMapping [] [] []



    cppDefaultValue = CPP.defaultValue cpp



    includeImport (Import path) = [lt|#include "#{dropExtension (slashForward path)}_types.h"|]



    optionalHeader (False, _) = mempty

    optionalHeader (True, header) = includeHeader header



    includeHeader header = [lt|#include #{header}|]



    includeEnum = if enumHeader then [lt|#include "#{file}_enum.h"|] else mempty



    -- True if declarations have any type satisfying f

    have f = getAny $ F.foldMap g declarations

      where

        g Struct{..} = F.foldMap (foldMapType f . fieldType) structFields

                    <> optional (foldMapType f) structBase

        g _ = mempty



    anyBonded (BT_Bonded _) = Any True

    anyBonded _ = Any False



    anyBlob BT_Blob = Any True

    anyBlob _ = Any False



    anyNullable = Any . isNullable



    anyStringOrContainer f = Any (isString f || isMetaName f || isContainer f)



    bondHeaders :: [(Bool, String)]

    bondHeaders = [

        (have anyNullable, "<bond/core/nullable.h>"),

        (have anyBonded, "<bond/core/bonded.h>"),

        (have anyBlob, "<bond/core/blob.h>"),

        (scoped_alloc_enabled && have anyStringOrContainer, "<scoped_allocator>")]



    -- forward declaration

    typeDeclaration f@Forward {..} = [lt|#{CPP.template f}struct #{declName};|]



    -- struct definition

    typeDeclaration s@Struct {..} = [lt|

    #{template}struct #{declName}#{optional base structBase}

    {

        #{optional allocatorType allocator}#{newlineSepEnd 2 field structFields}#{defaultCtor}



        #{copyCtor}#{ifThenElse alloc_ctors_enabled (optional allocatorCopyCtor allocator) mempty}

        #{moveCtor}#{ifThenElse alloc_ctors_enabled (optional allocatorMoveCtor allocator) mempty}

        #{optional allocatorCtor allocator}

        #{assignmentOp}



        bool operator==(const #{declName}&#{otherParam}) const

        {

            return true#{optional baseEqual structBase}#{newlineBeginSep 4 fieldEqual structFields};

        }



        bool operator!=(const #{declName}& #{otherParamName}) const

        {

            return !(*this == #{otherParamName});

        }



        void swap(#{declName}&#{otherParam})

        {

            using std::swap;#{optional swapBase structBase}#{newlineBeginSep 3 swapField structFields}

        }



        struct Schema;



    protected:

        #{initMetadata}

    };



    #{template}inline void swap(#{qualifiedClassName}& #{leftParamName}, #{qualifiedClassName}& #{rightParamName})

    {

        #{leftParamName}.swap(#{rightParamName});

    }|]

      where

        template = CPP.template s

        qualifiedClassName = CPP.qualifiedClassName cpp s



        fieldNames :: [String]

        fieldNames = foldMapStructFields (return . fieldName) s



        otherParamName = uniqueName "other" fieldNames

        otherParam = if hasOnlyMetaFields then mempty else ' ':otherParamName

        hasOnlyMetaFields = not (any (not . getAny . metaField) structFields) && isNothing structBase

        hasMetaFields = getAny $ foldMapStructFields metaField s



        base x = [lt|

      : #{cppType x}|]



        field Field {..} = [lt|#{cppType fieldType} #{fieldName};|]



        notMeta Field {fieldType = BT_MetaName, ..} _ = [lt|/* skip bond_meta::name field '#{fieldName}' */|]

        notMeta Field {fieldType = BT_MetaFullName, ..} _ = [lt|/* skip bond_meta::full_name field '#{fieldName}' */|]

        notMeta _ f = f



        fieldEqual f@Field {..} = notMeta f [lt|&& (#{fieldName} ==#{otherParam}.#{fieldName})|]



        baseEqual b = [lt|

                && (static_cast<const #{cppType b}&>(*this) == static_cast<const #{cppType b}&>(#{otherParam}))|]



        swapField f@Field {..} = notMeta f [lt|swap(#{fieldName},#{otherParam}.#{fieldName});|]



        swapBase b = [lt|

            #{cppType b}::swap(#{otherParam});|]



        -- value to pass to field initializer in ctor initialize list

        -- or Nothing if field doesn't need explicit initialization

        initValue (BT_Maybe _) _ = Nothing

        initValue t (Just d) = Just $ cppDefaultValue t d

        initValue (BT_TypeParam _) _ = Just mempty

        initValue (BT_UserDefined a@Alias {} args) d

            | customAliasMapping cpp a = Just mempty

            | otherwise = initValue (resolveAlias a args) d

        initValue t _

            | isScalar t = Just mempty

            | otherwise = Nothing



        -- constructor initializer list from 'base' and 'fields' initializers

        initializeList base' fields = between colon mempty $ commaLineSep 3 id [base', fields]

          where

            colon = [lt|

          : |]



        -- constructor body

        ctorBody = if hasMetaFields then [lt|

        {

            InitMetadata("#{declName}", "#{getDeclTypeName idl s}");

        }|]

            else [lt|

        {

        }|]



        needAlloc alloc = isJust structBase || any (allocParameterized alloc . fieldType) structFields



        allocParameterized alloc (BT_Nullable t) = allocParameterized alloc t

        allocParameterized alloc t = (isStruct t) || (L.isInfixOf (L.pack alloc) $ toLazyText $ cppTypeExpandAliases t)



        -- default constructor

        defaultCtor = [lt|

        #{dummyTemplateTag}#{declName}(#{vc12WorkaroundParam})#{initList}#{ctorBody}|]

          where

            needAllocParam = maybe False needAlloc allocator



            vc12WorkaroundParam = if needAllocParam then [lt|_bond_vc12_ctor_workaround_ = {}|] else mempty



            dummyTemplateTag = if needAllocParam

                then [lt|struct _bond_vc12_ctor_workaround_ {};

        template <int = 0> // Workaround to avoid compilation if not used

        |]

                else mempty



            initList = initializeList mempty

                $ commaLineSep 3 fieldInit structFields

            fieldInit Field {..} = optional (\x -> [lt|#{fieldName}(#{x})|])

                $ initValue fieldType fieldDefault



        allocatorType alloc = [lt|using allocator_type = #{alloc};



        |]



        allocatorCtor alloc = [lt|

        explicit

        #{declName}(const #{alloc}&#{allocParam})#{initList}#{ctorBody}

        |]

          where

            allocParam = if needAlloc alloc then [lt| allocator|] else mempty

            initList = initializeList

                (optional baseInit structBase)

                (commaLineSep 3 fieldInit structFields)

            baseInit b = [lt|#{cppType b}(allocator)|]

            fieldInit Field {..} = optional (\x -> [lt|#{fieldName}(#{x})|])

                $ allocInitValue fieldType fieldDefault

            allocInitValue t@(BT_UserDefined a@Alias {} args) d

                | allocParameterized alloc t = allocInitValue (resolveAlias a args) d

                | otherwise = initValue t d

            allocInitValue (BT_Nullable t) _ = allocInitValue t Nothing

            allocInitValue (BT_Maybe t) _ = allocInitValue t Nothing

            allocInitValue t (Just d)

                | isString t = Just [lt|#{cppDefaultValue t d}, allocator|]

            allocInitValue t Nothing

                | isContainer t || isMetaName t || isString t || isStruct t = Just "allocator"

            allocInitValue t d = initValue t d



        -- copy constructor

        copyCtor = if hasMetaFields then define else implicitlyDeclared

          where

            -- default OK when there are no meta fields

            implicitlyDeclared = [lt|

        // Compiler generated copy ctor OK

        #{declName}(const #{declName}&) = default;|]



            -- define ctor to initialize meta fields

            define = [lt|#{declName}(const #{declName}& #{otherParamName})#{initList}#{ctorBody}|]

              where

                initList = initializeList

                    (optional baseCopy structBase)

                    (commaLineSep 3 fieldCopy structFields)

                baseCopy b = [lt|#{cppType b}(#{otherParamName})|]

                fieldCopy Field {..} = [lt|#{fieldName}(#{otherParamName}.#{fieldName}#{getAllocator fieldType})|]

                getAllocator BT_MetaName = [lt|.get_allocator()|]

                getAllocator BT_MetaFullName =  [lt|.get_allocator()|]

                getAllocator _ = mempty



        -- copy/move constructor with allocator

        allocatorCopyOrMoveCtor otherParamDecl otherParamValue alloc = [lt|



        #{declName}(#{otherParamDecl declName}#{otherParam}, const #{alloc}&#{allocParam})#{initList}#{ctorBody}|]

          where

            allocParam = if needAlloc alloc then [lt| allocator|] else mempty



            initList = initializeList

                (optional baseInit structBase)

                (commaLineSep 3 fieldInit structFields)

            baseInit b = [lt|#{cppType b}(#{otherParamValue $ L.pack otherParamName}, allocator)|]



            fieldRef fieldName = [lt|#{otherParamName}.#{fieldName}|]

            fieldInit Field {..} = [lt|#{fieldName}(#{otherParamValue $ fieldRef fieldName}#{allocInitValueText fieldType})|]



            allocInitValueText fieldType = optional (\x -> [lt|, #{x}|])

                $ allocInitValue fieldType

            allocInitValue t@(BT_UserDefined a@Alias {} args)

                | allocParameterized alloc t = allocInitValue (resolveAlias a args)

                | otherwise = Nothing

            allocInitValue (BT_Nullable t) = allocInitValue t

            allocInitValue (BT_Maybe t) = allocInitValue t

            allocInitValue t

                | isList t || isMetaName t || isString t || isStruct t || isAssociative t = Just [lt|allocator|]

                | otherwise = Nothing



        -- copy constructor with allocator

        allocatorCopyCtor alloc = allocatorCopyOrMoveCtor (\f -> [lt|const #{f}&|]) id alloc



        -- move constructor

        moveCtor = if hasMetaFields then [lt|

        #{explicit}|]

            -- even if implicit would be okay, fall back to explicit for

            -- compilers that don't support = default for move constructors

                                    else [lt|

#if defined(_MSC_VER) && (_MSC_VER < 1900)  // Versions of MSVC prior to 1900 do not support = default for move ctors

        #{explicit}

#else

        #{implicit}

#endif|]

          where

            -- default OK when there are no meta fields

            implicit = [lt|#{declName}(#{declName}&&) = default;|]



            -- define ctor to perform member-by-member move and--if

            -- needed--initialize meta fields

            explicit = [lt|#{declName}(#{declName}&&#{param})#{initList}#{ctorBody}|]

            initList = initializeList

                (optional baseMove structBase)

                (commaLineSep 3 fieldMove structFields)

            baseMove b = [lt|#{cppType b}(std::move(#{otherParamName}))|]

            fieldMove Field {..} = [lt|#{fieldName}(std::move(#{otherParamName}.#{fieldName}))|]

            param = if initList == mempty then mempty else ' ':otherParamName



        -- move constructor with allocator

        allocatorMoveCtor alloc = (allocatorCopyOrMoveCtor (\f -> [lt|#{f}&&|]) (\f -> [lt|std::move(#{f})|]) alloc)



        -- operator=

        assignmentOp = if hasMetaFields then define else implicitlyDeclared

          where

            -- default OK when there are no meta fields

            implicitlyDeclared = [lt|

#if defined(_MSC_VER) && (_MSC_VER < 1900)  // Versions of MSVC prior to 1900 do not support = default for move ctors

        #{define}

#else

        // Compiler generated operator= OK

        #{declName}& operator=(const #{declName}&) = default;

        #{declName}& operator=(#{declName}&&) = default;

#endif|]



            -- define operator= using swap

            define = [lt|#{declName}& operator=(#{declName} #{otherParamName})

        {

            #{otherParamName}.swap(*this);

            return *this;

        }|]



        initMetadata = [lt|void InitMetadata(const char*#{nameParam}, const char*#{qualifiedNameParam})

        {#{newlineBeginSep 3 id [baseInit, nameInit, qualifiedInit]}

        }|]

          where

            nameParam = if baseInit == mempty && nameInit == mempty then mempty else uniqueName "name" fieldNames

            qualifiedNameParam = if baseInit == mempty && qualifiedInit == mempty then mempty else uniqueName "qual_name" fieldNames

            baseInit = optional (\b -> [lt|#{cppType b}::InitMetadata(#{nameParam}, #{qualifiedNameParam});|]) structBase

            nameInit = newlineSep 3 init' structFields

              where

                init' Field {fieldType = BT_MetaName, ..} = [lt|this->#{fieldName} = #{nameParam};|]

                init' _ = mempty

            qualifiedInit = newlineSep 3 init' structFields

              where

                init' Field {fieldType = BT_MetaFullName, ..} = [lt|this->#{fieldName} = #{qualifiedNameParam};|]

                init' _ = mempty



        leftParamName = uniqueName "left" fieldNames

        rightParamName = uniqueName "right" fieldNames



    -- enum definition and helpers

    typeDeclaration e@Enum {..} = [lt|

    namespace _bond_enumerators

    {

    namespace #{declName}

    {

        #{enumDefinition}

        inline BOND_CONSTEXPR const char* GetTypeName(enum #{declName})

        {

            return "#{declName}";

        }



        inline BOND_CONSTEXPR const char* GetTypeName(enum #{declName}, const ::bond::qualified_name_tag&)

        {

            return "#{getDeclTypeName idl e}";

        }



#if defined(_MSC_VER) && (_MSC_VER < 1900) // Versions of MSVC prior to 1900 do not support magic statics

        extern const std::map<enum #{declName}, std::string> _value_to_name_#{declName};



        inline const std::map<enum #{declName}, std::string>& GetValueToNameMap(enum #{declName})

        {

            return _value_to_name_#{declName};

        }



        extern const std::map<std::string, enum #{declName}> _name_to_value_#{declName};



        inline const std::map<std::string, enum #{declName}>& GetNameToValueMap(enum #{declName})

        {

            return _name_to_value_#{declName};

        }

#else

        template <typename Map = std::map<enum #{declName}, std::string> >

        inline const Map& GetValueToNameMap(enum #{declName}, ::bond::detail::mpl::identity<Map> = {})

        {

            static const Map s_valueToNameMap

                {

                    #{CPP.enumValueToNameInitList 5 e}

                };

            return s_valueToNameMap;

        }



        template <typename Map = std::map<std::string, enum #{declName}> >

        inline const Map& GetNameToValueMap(enum #{declName}, ::bond::detail::mpl::identity<Map> = {})

        {

            static const Map s_nameToValueMap

                {

                    #{CPP.enumNameToValueInitList 5 e}

                };

            return s_nameToValueMap;

        }

#endif

        #{export_attr}const std::string& ToString(enum #{declName} value);



        #{export_attr}void FromString(const std::string& name, enum #{declName}& value);



        #{export_attr}bool ToEnum(enum #{declName}& value, const std::string& name);



        #{export_attr}bool FromEnum(std::string& name, enum #{declName} value);



    } // namespace #{declName}

    } // namespace _bond_enumerators



    #{enumUsing}|]

      where

        enumDefinition = if enumHeader then mempty else [lt|#{CPP.enumDefinition e}

        |]

        enumUsing = if enumHeader then mempty else [lt|using namespace _bond_enumerators::#{declName};

    |]

        export_attr = optional (\a -> [lt|#{a} |]) export_attribute



    typeDeclaration _ = mempty