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