{-|
Module      : OCaml.Internal.Common
Description : Internal utility functions
Copyright   : Plow Technologies, 2017
License     : BSD3
Maintainer  : mchaver@gmail.com
Stability   : experimental

-}

{-# LANGUAGE OverloadedStrings #-}

module OCaml.Internal.Common where

-- base
import Data.Char (toLower,toUpper)
import qualified Data.List as L
import Data.Monoid ((<>))

-- aeson
import qualified Data.Aeson.Types as Aeson (Options(..), defaultOptions)

-- containers
import qualified Data.Map.Strict as Map

-- formatting
import Formatting hiding (stext, text)

-- ocaml-export
import OCaml.BuckleScript.Types (HaskellTypeMetaData, OCamlTypeMetaData)

-- text
import Data.Text  (Text)
import qualified Data.Text as T
import qualified Data.Text.Lazy as LT

-- wl-pprint
import Text.PrettyPrint.Leijen.Text
  (Doc, (<+>), (<$$>), comma, line, linebreak, nest, text, displayT, renderPretty)


-- | For URLs and POSIX systems.
(</>) :: Text -> Text -> Text
(</>) a b =
    if hasEndingSeparator sA
    then
      if hasLeadingSeparator sB
      then T.pack $ sA ++ drop 1 sB
      else T.pack $ sA ++ sB
    else
      T.pack $ sA ++ "/" ++ sB
  where
    sA = T.unpack a
    sB = T.unpack b

    hasEndingSeparator :: String -> Bool
    hasEndingSeparator lst@(_hd:_tl) = (==) '/' . last $ lst
    hasEndingSeparator [] = False

    hasLeadingSeparator :: String -> Bool
    hasLeadingSeparator (hd:_tl) = hd == '/'
    hasLeadingSeparator [] = False

infix 5 </>


data Options = Options
  { includeOCamlInterface :: Bool
  , aesonOptions :: Aeson.Options
  , dependencies :: Map.Map HaskellTypeMetaData OCamlTypeMetaData
  }

defaultOptions :: Options
defaultOptions =
  Options
    { includeOCamlInterface = False
    , aesonOptions = Aeson.defaultOptions
    , dependencies = Map.empty
    }

data TypeMetaData = TypeMetaData
  { topLevelOCamlTypeMetaData :: Maybe OCamlTypeMetaData -- Just if not primitive
  , userOptions :: Options
  }

cr :: Format r r
cr = now "\n"

mintercalate :: Monoid m => m -> [m] -> m
mintercalate _ [] = mempty
mintercalate _ [x] = x
mintercalate separator (x:xs) = x <> separator <> mintercalate separator xs

msuffix :: Monoid m => m -> [m] -> m
msuffix _ [] = mempty
msuffix suffix [x] = x <> suffix
msuffix suffix (x:xs) = x <> suffix <> msuffix suffix xs

mconcatWith :: Monoid m => m -> m -> [m] -> m
mconcatWith _   _   []     = mempty
mconcatWith pre suf [x]    = pre <> x <> suf
mconcatWith pre suf (x:xs) = pre <> x <> suf <> mconcatWith pre suf xs

mintercalatefinish :: Monoid m => m -> m -> [m] -> m
mintercalatefinish _ _ [] = mempty
mintercalatefinish _ f [x] = x <> f
mintercalatefinish separator f (x:xs) = x <> separator <> (mintercalatefinish separator f xs)

pprinter :: Doc -> Text
pprinter = LT.toStrict . displayT . renderPretty 0.4 100

stext :: Data.Text.Text -> Doc
stext = text . LT.fromStrict

spaceparens :: Doc -> Doc
spaceparens doc = "(" <+> doc <+> ")"


-- | Parentheses of which the right parenthesis exists on a new line
newlineparens :: Doc -> Doc
newlineparens doc = "(" <> doc <$$> ")"

-- | An empty line, regardless of current indentation
emptyline :: Doc
emptyline = nest minBound linebreak

-- | Like <$$>, but with an empty line in between
(<$+$>) :: Doc -> Doc -> Doc
l <$+$> r = l <> emptyline <$$> r

linesBetween :: [Doc] -> Doc
linesBetween docs =
  let extra = if length docs > 0 then line else ""
  in foldl (<>) "" $ (L.intersperse (line <> line) docs) <> [extra]

squarebracks :: Doc -> Doc
squarebracks doc = "[" <+> doc <+> "]"

arraybrackets :: Doc -> Doc
arraybrackets doc = "[|" <+> doc <+> "|]"

pair :: Doc -> Doc -> Doc
pair l r = spaceparens $ l <> comma <+> r

lowercaseFirst :: String -> String
lowercaseFirst (hd:tl) = toLower hd : tl
lowercaseFirst [] = []

uppercaseFirst :: String -> String
uppercaseFirst (hd:tl) = toUpper hd : tl
uppercaseFirst [] = []

textLowercaseFirst :: Text -> Text
textLowercaseFirst = T.pack . lowercaseFirst . T.unpack

textUppercaseFirst :: Text -> Text
textUppercaseFirst = T.pack . uppercaseFirst . T.unpack

mkDocList :: [Doc] -> Doc
mkDocList ds =
  foldl (<>) "" $
    if length ds > 1
      then ["("] <> (L.intersperse ", " ds) <> [")"]
      else ds

mkDocListP :: [Doc] -> Doc
mkDocListP ds =
  foldl (<>) "" $
    if length ds > 0
      then ["("] <> (L.intersperse ", " ds) <> [")"]
      else ds

foldMod :: [Text] -> Text
foldMod = T.intercalate "."