-- 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
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