{- |
    Module      :  $Header$
    Description :  File names for several intermediate file formats.
    Copyright   :  (c) 2009        Holger Siegel
                       2013 - 2014 Björn Peemöller
                       2018        Kai-Oliver Prott
    License     :  BSD-3-clause

    Maintainer  :  fte@informatik.uni-kiel.de
    Stability   :  experimental
    Portability :  portable

    The functions in this module were collected from several compiler modules
    in order to provide a unique accessing point for this functionality.
-}
module Curry.Files.Filenames
  ( -- * Re-exports from 'System.FilePath'
    FilePath, takeBaseName, dropExtension, takeExtension, takeFileName

    -- * Conversion between 'ModuleIdent' and 'FilePath'
  , moduleNameToFile, fileNameToModule, splitModuleFileName, isCurryFilePath

    -- * Curry sub-directory
  , currySubdir, hasCurrySubdir, addCurrySubdir, addCurrySubdirModule
  , ensureCurrySubdir

    -- * File name extensions
    -- ** Curry files
  , curryExt, lcurryExt, icurryExt

    -- ** FlatCurry files
  , typedFlatExt, flatExt, flatIntExt

    -- ** AbstractCurry files
  , acyExt, uacyExt

    -- ** Source and object files
  , sourceRepExt, sourceExts, moduleExts

    -- * Functions for computing file names
  , interfName, typedFlatName, typeAnnFlatName, flatName, flatIntName
  , acyName, uacyName, sourceRepName, tokensName, commentsName
  , astName, shortASTName, htmlName
  ) where

import System.FilePath

import Curry.Base.Ident

-- -----------------------------------------------------------------------------
-- Conversion between ModuleIdent and FilePath
-- -----------------------------------------------------------------------------

-- |Create a 'FilePath' from a 'ModuleIdent' using the hierarchical module
-- system
moduleNameToFile :: ModuleIdent -> FilePath
moduleNameToFile = foldr1 (</>) . midQualifiers

-- |Extract the 'ModuleIdent' from a 'FilePath'
fileNameToModule :: FilePath -> ModuleIdent
fileNameToModule = mkMIdent . splitDirectories . dropExtension . dropDrive

-- |Split a 'FilePath' into a prefix directory part and those part that
-- corresponds to the 'ModuleIdent'. This is especially useful for
-- hierarchically module names.
splitModuleFileName :: ModuleIdent -> FilePath -> (FilePath, FilePath)
splitModuleFileName m fn = case midQualifiers m of
  [_] -> splitFileName fn
  ms  -> let (base, ext) = splitExtension fn
             dirs        = splitDirectories base
             (pre, suf)  = splitAt (length dirs - length ms) dirs
             path        = if null pre then ""
                                       else addTrailingPathSeparator (joinPath pre)
         in  (path, joinPath suf <.> ext)

-- |Checks whether a 'String' represents a 'FilePath' to a Curry module
isCurryFilePath :: String -> Bool
isCurryFilePath str =  isValid str
                    && takeExtension str `elem` ("" : moduleExts)

-- -----------------------------------------------------------------------------
-- Curry sub-directory
-- -----------------------------------------------------------------------------

-- |The standard hidden subdirectory for curry files
currySubdir :: String
currySubdir = ".curry"

-- |Does the given 'FilePath' contain the 'currySubdir'
-- as its last directory component?
hasCurrySubdir :: FilePath -> Bool
hasCurrySubdir f = not (null dirs) && last dirs == currySubdir
  where dirs = splitDirectories $ takeDirectory f

-- |Add the 'currySubdir' to the given 'FilePath' if the flag is 'True' and
-- the path does not already contain it, otherwise leave the path untouched.
addCurrySubdir :: Bool -> FilePath -> FilePath
addCurrySubdir b fn = if b then ensureCurrySubdir fn else fn

-- |Add the 'currySubdir' to the given 'FilePath' if the flag is 'True' and
-- the path does not already contain it, otherwise leave the path untouched.
addCurrySubdirModule :: Bool -> ModuleIdent -> FilePath -> FilePath
addCurrySubdirModule b m fn
  | b         = let (pre, file) = splitModuleFileName m fn
                in  ensureCurrySubdir pre </> file
  | otherwise = fn

-- | Ensure that the 'currySubdir' is the last component of the
-- directory structure of the given 'FilePath'. If the 'FilePath' already
-- contains the sub-directory, it remains unchanged.
ensureCurrySubdir :: FilePath -- ^ original 'FilePath'
                  -> FilePath -- ^ new 'FilePath'
ensureCurrySubdir fn = normalise $ addSub (splitDirectories d) </> f
  where
  (d, f) = splitFileName fn
  addSub dirs | null dirs                = currySubdir
              | last dirs == currySubdir = joinPath dirs
              | otherwise                = joinPath dirs </> currySubdir

-- -----------------------------------------------------------------------------
-- File name extensions
-- -----------------------------------------------------------------------------

-- |Filename extension for non-literate curry files
curryExt :: String
curryExt = ".curry"

-- |Filename extension for literate curry files
lcurryExt :: String
lcurryExt = ".lcurry"

-- |Filename extension for curry interface files
icurryExt :: String
icurryExt = ".icurry"

-- |Filename extension for curry source files.
--
-- /Note:/ The order of the extensions defines the order in which source files
-- should be searched for, i.e. given a module name @M@, the search order
-- should be the following:
--
-- 1. @M.curry@
-- 2. @M.lcurry@
--
sourceExts :: [String]
sourceExts = [curryExt, lcurryExt]

-- |Filename extension for curry module files
-- TODO: Is the order correct?
moduleExts :: [String]
moduleExts = sourceExts ++ [icurryExt]

-- |Filename extension for typed flat-curry files
typedFlatExt :: String
typedFlatExt = ".tfcy"

-- |Filename extension for type-annotated flat-curry files
typeAnnFlatExt :: String
typeAnnFlatExt = ".tafcy"

-- |Filename extension for flat-curry files
flatExt :: String
flatExt = ".fcy"

-- |Filename extension for extended-flat-curry interface files
flatIntExt :: String
flatIntExt = ".fint"

-- |Filename extension for abstract-curry files
acyExt :: String
acyExt = ".acy"

-- |Filename extension for untyped-abstract-curry files
uacyExt :: String
uacyExt = ".uacy"

-- |Filename extension for curry source representation files
sourceRepExt :: String
sourceRepExt = ".cy"

-- |Filename extension for token files
tokensExt :: String
tokensExt = ".tokens"

-- |Filename extension for comment token files
commentsExt :: String
commentsExt = ".cycom"

-- |Filename extension for AST files
astExt :: String
astExt = ".ast"

-- |Filename extension for shortened AST files
shortASTExt :: String
shortASTExt = ".sast"

-- ---------------------------------------------------------------------------
-- Computation of file names for a given source file
-- ---------------------------------------------------------------------------

-- |Compute the filename of the interface file for a source file
interfName :: FilePath -> FilePath
interfName = replaceExtensionWith icurryExt

-- |Compute the filename of the typed flat curry file for a source file
typedFlatName :: FilePath -> FilePath
typedFlatName = replaceExtensionWith typedFlatExt

-- |Compute the filename of the typed flat curry file for a source file
typeAnnFlatName :: FilePath -> FilePath
typeAnnFlatName = replaceExtensionWith typeAnnFlatExt

-- |Compute the filename of the flat curry file for a source file
flatName :: FilePath -> FilePath
flatName = replaceExtensionWith flatExt

-- |Compute the filename of the flat curry interface file for a source file
flatIntName :: FilePath -> FilePath
flatIntName = replaceExtensionWith flatIntExt

-- |Compute the filename of the abstract curry file for a source file
acyName :: FilePath -> FilePath
acyName = replaceExtensionWith acyExt

-- |Compute the filename of the untyped abstract curry file for a source file
uacyName :: FilePath -> FilePath
uacyName = replaceExtensionWith uacyExt

-- |Compute the filename of the source representation file for a source file
sourceRepName :: FilePath -> FilePath
sourceRepName = replaceExtensionWith sourceRepExt

-- |Compute the filename of the tokens file for a source file
tokensName :: FilePath -> FilePath
tokensName = replaceExtensionWith tokensExt

-- |Compute the filename of the comment tokens file for a source file
commentsName :: FilePath -> FilePath
commentsName = replaceExtensionWith commentsExt

-- |Compute the filename of the ast file for a source file
astName :: FilePath -> FilePath
astName = replaceExtensionWith astExt

-- |Compute the filename of the ast file for a source file
shortASTName :: FilePath -> FilePath
shortASTName = replaceExtensionWith shortASTExt

-- |Compute the filename of the HTML file for a source file
htmlName :: ModuleIdent -> String
htmlName m = moduleName m ++ "_curry.html"

-- |Replace a filename extension with a new extension
replaceExtensionWith :: String -> FilePath -> FilePath
replaceExtensionWith = flip replaceExtension