-- 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.Cs.Util

    ( typeAttributes

    , propertyAttributes

    , schemaAttributes

    , paramConstraints

    , defaultValue

    , disableCscWarnings

    , disableReSharperWarnings

    ) where



import Data.Int (Int64)

import Data.Monoid

import Prelude

import Data.Text.Lazy (Text)

import Text.Shakespeare.Text

import Paths_bond (version)

import Data.Version (showVersion)

import Language.Bond.Syntax.Types

import Language.Bond.Syntax.Util

import Language.Bond.Codegen.TypeMapping

import Language.Bond.Codegen.Util



disableCscWarnings :: Text

disableCscWarnings = [lt|

// suppress "Missing XML comment for publicly visible type or member"

#pragma warning disable 1591

|]



disableReSharperWarnings :: Text

disableReSharperWarnings = [lt|

#region ReSharper warnings

// ReSharper disable PartialTypeWithSinglePart

// ReSharper disable RedundantNameQualifier

// ReSharper disable InconsistentNaming

// ReSharper disable CheckNamespace

// ReSharper disable UnusedParameter.Local

// ReSharper disable RedundantUsingDirective

#endregion

|]



-- C# field/property attributes

propertyAttributes :: MappingContext -> Field -> Text

propertyAttributes cs Field {..} =

    schemaAttributes 2 fieldAttributes

 <> [lt|[global::Bond.Id(#{fieldOrdinal})#{typeAttribute}#{modifierAttribute fieldType fieldModifier}]

        |]

        where

            annotatedType = getAnnotatedTypeName cs fieldType

            propertyType = getTypeName cs fieldType

            typeAttribute = if annotatedType /= propertyType

                then [lt|, global::Bond.Type(typeof(#{annotatedType}))|]

                else mempty

            modifierAttribute BT_MetaName _ = [lt|, global::Bond.RequiredOptional|]

            modifierAttribute BT_MetaFullName _ = [lt|, global::Bond.RequiredOptional|]

            modifierAttribute _ Required = [lt|, global::Bond.Required|]

            modifierAttribute _ RequiredOptional = [lt|, global::Bond.RequiredOptional|]

            modifierAttribute _ _ = mempty



-- C# class/struct/interface attributes

typeAttributes :: MappingContext -> Declaration -> Text

typeAttributes cs s@Struct {..} =

    optionalTypeAttributes cs s

 <> [lt|[global::Bond.Schema]

    |]

 <> generatedCodeAttr



-- C# enum attributes

typeAttributes cs e@Enum {..} =

    optionalTypeAttributes cs e

 <> generatedCodeAttr



-- C# service attributes

typeAttributes cs s@Service {..} =

    optionalTypeAttributes cs s

    <> generatedCodeAttr



typeAttributes _ _ = error "typeAttributes: impossible happened."



generatedCodeAttr :: Text

generatedCodeAttr = [lt|[System.CodeDom.Compiler.GeneratedCode("gbc", "#{showVersion version}")]

    |]



idl :: MappingContext

idl = MappingContext idlTypeMapping [] [] []



optionalTypeAttributes :: MappingContext -> Declaration -> Text

optionalTypeAttributes cs decl =

    schemaAttributes 1 (declAttributes decl)

 <> namespaceAttribute

  where

    namespaceAttribute = if getDeclNamespace idl decl == getDeclNamespace cs decl

        then mempty

        else [lt|[global::Bond.Namespace("#{getQualifiedName idl $ getDeclNamespace idl decl}")]

    |]



-- Attributes defined by the user in the schema

schemaAttributes :: Int64 -> [Attribute] -> Text

schemaAttributes indent_ = newlineSepEnd indent_ schemaAttribute

  where

    schemaAttribute Attribute {..} =

        [lt|[global::Bond.Attribute("#{getQualifiedName idl attrName}", "#{attrValue}")]|]



-- generic type parameter constraints

paramConstraints :: [TypeParam] -> Text

paramConstraints = newlineBeginSep 2 constraint

  where

    constraint (TypeParam _ Nothing) = mempty

    constraint (TypeParam name (Just Value)) = [lt|where #{name} : struct|]



-- Initial value for C# field/property or Nothing if C# implicit default is OK

defaultValue :: MappingContext -> Field -> Maybe Text

defaultValue cs Field {fieldDefault = Nothing, ..} = implicitDefault fieldType

  where

    newInstance t = Just [lt|new #{getInstanceTypeName cs t}()|]

    implicitDefault (BT_Bonded t) = Just [lt|global::Bond.Bonded<#{getTypeName cs t}>.Empty|]

    implicitDefault t@(BT_TypeParam _) = Just [lt|global::Bond.GenericFactory.Create<#{getInstanceTypeName cs t}>()|]

    implicitDefault t@BT_Blob = newInstance t

    implicitDefault t@(BT_UserDefined a@Alias {..} args)

        | customAliasMapping cs a = newInstance t

        | otherwise = implicitDefault $ resolveAlias a args

    implicitDefault t

        | isString t = Just [lt|""|]

        | isContainer t || isStruct t = newInstance t

    implicitDefault _ = Nothing



defaultValue cs Field {fieldDefault = (Just def), ..} = explicitDefault def

  where

    explicitDefault (DefaultInteger x) = Just [lt|#{x}|]

    explicitDefault (DefaultFloat x) = Just $ floatLiteral fieldType x

      where

        floatLiteral BT_Float y = [lt|#{y}F|]

        floatLiteral BT_Double y = [lt|#{y}|]

        floatLiteral _ _ = error "defaultValue/floatLiteral: impossible happened."

    explicitDefault (DefaultBool True) = Just "true"

    explicitDefault (DefaultBool False) = Just "false"

    explicitDefault (DefaultString x) = Just [lt|"#{x}"|]

    explicitDefault (DefaultEnum x) = Just [lt|#{getTypeName cs fieldType}.#{x}|]

    explicitDefault _ = Nothing