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

{-# OPTIONS_GHC -fno-warn-orphans #-}



{-|
Copyright   : (c) Microsoft
License     : MIT
Maintainer  : adamsap@microsoft.com
Stability   : provisional
Portability : portable

Helper functions for creating common structures useful in code generation.
These functions often operate on 'Text' objects.
-}



module Language.Bond.Codegen.Util

    ( commonHeader

    , commaSep

    , newlineSep

    , commaLineSep

    , newlineSepEnd

    , newlineBeginSep

    , doubleLineSep

    , doubleLineSepEnd

    , uniqueName

    , uniqueNames

    , indent

    , newLine

    , slashForward

    ) where



import Data.Int (Int64)

import Data.Word

import Prelude

import Data.Text.Lazy (Text, justifyRight)

import Text.Shakespeare.Text

import Paths_bond (version)

import Data.Version (showVersion)

import Language.Bond.Util



instance ToText Word16 where

    toText = toText . show



instance ToText Double where

    toText = toText . show



instance ToText Integer where

    toText = toText . show



indent :: Int64 -> Text

indent n = justifyRight (4 * n) ' ' ""



commaLine :: Int64 -> Text

commaLine n = [lt|,

#{indent n}|]



newLine :: Int64 -> Text

newLine n = [lt|

#{indent n}|]



doubleLine :: Int64 -> Text

doubleLine n = [lt|



#{indent n}|]



-- | Separates elements of a list with a comma.

commaSep :: (a -> Text) -> [a] -> Text

commaSep = sepBy ", "



newlineSep, commaLineSep, newlineSepEnd, newlineBeginSep, doubleLineSep, doubleLineSepEnd

    :: Int64 -> (a -> Text) -> [a] -> Text



-- | Separates elements of a list with new lines. Starts new lines at the

-- specified indentation level.

newlineSep = sepBy . newLine



-- | Separates elements of a list with comma followed by a new line. Starts

-- new lines at the specified indentation level.

commaLineSep = sepBy . commaLine



-- | Separates elements of a list with new lines, ending with a new line.

-- Starts new lines at the specified indentation level.

newlineSepEnd = sepEndBy . newLine



-- | Separates elements of a list with new lines, beginning with a new line.

-- Starts new lines at the specified indentation level.

newlineBeginSep = sepBeginBy . newLine



-- | Separates elements of a list with two new lines. Starts new lines at

-- the specified indentation level.

doubleLineSep = sepBy . doubleLine



-- | Separates elements of a list with two new lines, ending with two new

-- lines. Starts new lines at the specified indentation level.

doubleLineSepEnd = sepEndBy . doubleLine



-- | Returns common header for generated files using specified single-line

-- comment lead character(s) and a file name.

commonHeader ::  ToText a => a -> a -> a -> Text

commonHeader c input output = [lt|

#{c}------------------------------------------------------------------------------

#{c} This code was generated by a tool.

#{c}

#{c}   Tool : Bond Compiler #{showVersion version}

#{c}   Input filename:  #{input}

#{c}   Output filename: #{output}

#{c}

#{c} Changes to this file may cause incorrect behavior and will be lost when

#{c} the code is regenerated.

#{c} <auto-generated />

#{c}------------------------------------------------------------------------------

|]



-- | Given an intended name and a list of already taken names, returns a

-- unique name. Assumes that it's legal to append digits to the end of the

-- intended name.

uniqueName :: String -> [String] -> String

uniqueName baseName taken = go baseName (0::Integer)

  where go name counter

          | not (name `elem` taken) = name

          | otherwise = go newName (counter + 1)

                        where newName = baseName ++ (show counter)



-- | Given a list of names with duplicates and a list of reserved names,

-- create a list of unique names using the uniqueName function.

uniqueNames :: [String] -> [String] -> [String]

uniqueNames names reservedInit = reverse $ go names [] reservedInit

  where

    go [] acc _ = acc

    go (name:remaining) acc reservedAcc = go remaining (newName:acc) (newName:reservedAcc)

      where

        newName = uniqueName name reservedAcc



-- | Converts all file path slashes to forward slashes.

slashForward :: String -> String

slashForward path = map replace path

  where replace '\\' = '/'

        replace c    = c