content: module Ivory.Artifact (
    -- * Ivory Artifacts
    -- | Artifacts permit the user to generate non-Ivory language files
    -- as part of an Ivory program's build process. Artifacts are typically
    -- used to encapsulate helper code (such as Ivory.Stdlib's string functions
    -- helper .c and .h files) or to generate debug or metadata output. The
    -- contents of an `Artifact` are given by some external file, or as a string.
    --
    -- Artifacts are exposed as an abstract type `Artifact` with a set of
    -- constructors, an accessor `artifactFileName`, and two functions,
    -- `putArtifact` and `printArtifact`, which are used to write an
    -- Artifacts to a file or print it to stdout, respectively.
    --
    -- Users may specify transformations on the contents of an artifact.
    -- Optionally, these transformations can throw an error. This is useful when
    -- using artifact files as templates.
    --
      Artifact()
    -- | Gives the file name that will be used when writing the `Artifact` to the
    -- filesystem.
    , artifactFileName
  
    -- * Artifact constructors
    -- | `artifactFile` creates an `Artifact` from an output filename of type
    -- `FilePath` and an input filepath of type `IO FilePath`. The output filename
    -- should be a simple filename, any directory information will be dropped. The
    -- input filepath should be an absolute path. The input filepath is in the IO
    -- monad for compatiblility with Cabal's `getDataDir` data files functionality.
    , artifactFile
  
    -- | `artifactCabalFile` creates an `Artifact` given an input directory of
    -- type `IO FilePath` and an input filepath, relative to the input directory
    -- in the preceeding argument, of type `FilePath`.
    --
    -- This function is designed to be used with a Cabal Paths_(packagename)
    -- `getDataDir`, which has type `IO FilePath`, and a path to a file inside the
    -- data directory.  It is implemented in terms of `artifactFileName`. The
    -- output filename is given by dropping directory information from the input
    -- filepath.
    , artifactCabalFile
  
    -- | `artifactText` creates an `Artifact` from an output filename of type
    -- `FilePath` and with contents of type `Text`.
    , artifactText
    -- | `artifactString` creates an `Artifact` from an output filename of type
    -- `FilePath` with contents of type `String`. Implemented in terms of
    -- `artifactText`.
    , artifactString
  
    -- | `artifactTransform` and `artifactTransformString` specify a
    -- transformation on the contents of an `Artifact`.
    , artifactTransform, artifactTransformString
  
    -- | `artifactTransformErr` and `artifactTransformErrString` specify a
    -- transformation on the contents of an `Artifact` which may give an error.
    , artifactTransformErr, artifactTransformErrString
  
    -- | `artifactPath` prepends a `FilePath` to the output file path.
    , artifactPath
  
    -- * Artifact actions
    -- | Takes a directory of type `FilePath` and an `Artifact`
    -- writes each `Artifact` to the file system or gives an error explaining why
    -- not. `Maybe String` containins errors encountered when an `Artifact` is
    -- transformed, or specified by an input filename which does not exist.
    , putArtifact
    -- | like `putArtifact` but ignores any errors.
    , putArtifact_
  
    -- | Takes an `Artifact` and prints it, or an appropriate error message, to
    -- stdout.
    , printArtifact
  
    -- | Takes a guess at whether two artifacts might be equal.
    , mightBeEqArtifact
  
    , Located(..)
    , mightBeEqLocatedArtifact
    ) where
  
  import Control.Monad (void)
  import qualified Data.Text.Lazy as T
  import qualified Data.Text.Lazy.IO as T
  import System.FilePath
  import System.Directory
  import Ivory.Artifact.Transformer
  import Ivory.Artifact.Location
  import System.IO.Unsafe (unsafePerformIO)
  
  data Artifact =
    Artifact
      { artifact_outputname  :: FilePath
      , artifact_contents    :: AContents
      , artifact_transformer :: Transformer T.Text
      }
  
  mightBeEqLocatedArtifact :: Located Artifact -> Located Artifact -> Bool
  mightBeEqLocatedArtifact (Root a) (Root b) = mightBeEqArtifact a b
  mightBeEqLocatedArtifact (Src a) (Src b) = mightBeEqArtifact a b
  mightBeEqLocatedArtifact (Incl a) (Incl b) = mightBeEqArtifact a b
  mightBeEqLocatedArtifact _ _ = False
  
  mightBeEqArtifact :: Artifact -> Artifact -> Bool
  mightBeEqArtifact a b =
    and [ artifact_outputname a == artifact_outputname b
        , artifact_contents   a `mightBeEqAContents` artifact_contents   b]
  
  data AContents = LiteralContents T.Text
                 | FileContents (IO FilePath)
  
  mightBeEqAContents :: AContents -> AContents -> Bool
  mightBeEqAContents (LiteralContents a) (LiteralContents b) = a == b
  mightBeEqAContents (FileContents a) (FileContents b) = unsafePerformIO a == unsafePerformIO b
  mightBeEqAContents _ _ = False
  
  artifactFileName :: Artifact -> FilePath
  artifactFileName = artifact_outputname
  
  artifactFile :: FilePath -> IO FilePath -> Artifact
  artifactFile outputname inputpath = Artifact
    { artifact_outputname  = takeFileName outputname
    , artifact_contents    = FileContents inputpath
    , artifact_transformer = emptyTransformer
    }
  
  artifactCabalFile :: IO FilePath -> FilePath -> Artifact
  artifactCabalFile inputdir inputfname =
    artifactFile (takeFileName inputfname)
                 (fmap (\i -> (i </> inputfname)) inputdir)
  
  artifactText :: FilePath -> T.Text -> Artifact
  artifactText outputname t = Artifact
    { artifact_outputname  = takeFileName outputname
    , artifact_contents    = LiteralContents t
    , artifact_transformer = emptyTransformer
    }
  
  artifactPath :: FilePath -> Artifact -> Artifact
  artifactPath f a = a { artifact_outputname = f </> artifact_outputname a }
  
  artifactString :: FilePath -> String -> Artifact
  artifactString f s = artifactText f (T.pack s)
  
  artifactTransform :: (T.Text -> T.Text) -> Artifact -> Artifact
  artifactTransform f a =
    a { artifact_transformer = transform f (artifact_transformer a) }
  
  artifactTransformString :: (String -> String) -> Artifact -> Artifact
  artifactTransformString f a = artifactTransform f' a
    where f' = T.pack . f . T.unpack
  
  artifactTransformErr :: (T.Text -> Either String T.Text) -> Artifact -> Artifact
  artifactTransformErr f a =
    a { artifact_transformer = transformErr f (artifact_transformer a) }
  
  artifactTransformErrString :: (String -> Either String String) -> Artifact -> Artifact
  artifactTransformErrString f a = artifactTransformErr f' a
    where f' t = fmap T.pack (f (T.unpack t))
  
  getArtifact :: Artifact -> IO (Either String T.Text)
  getArtifact a = g (artifact_contents a)
    where
    runT t = runTransformer (artifact_transformer a) t
    g (LiteralContents t) = return (runT t)
    g (FileContents getf) = do
        srcpath <- getf
        -- Check if srcpath exists. If it does not, give an error
        exists <- doesFileExist srcpath
        case exists of
          True -> do
            t <- T.readFile srcpath
            return (runT t)
          False -> return (Left (notfound srcpath))
  
    notfound srcpath = "Path " ++ srcpath ++ " for Artifact named "
       ++ artifact_outputname a ++ " could not be found."
  
  withContents :: Artifact -> (T.Text -> IO ()) -> IO (Maybe String)
  withContents a f = do
    contents <- getArtifact a
    case contents of
      Left err -> return (Just err)
      Right c  -> f c >> return Nothing
  
  putArtifact :: FilePath -> Artifact -> IO (Maybe String)
  putArtifact fp a = withContents a $ \c -> do
    let fname = fp </> artifact_outputname a
    createDirectoryIfMissing True (dropFileName fname)
    T.writeFile fname c
  
  putArtifact_ :: FilePath -> Artifact -> IO ()
  putArtifact_ fp a = void (putArtifact fp a)
  
  printArtifact :: Artifact -> IO ()
  printArtifact a = do
    res <- withContents a aux
    case res of
      Nothing -> return ()
      Just err -> putStrLn $
        "Encountered error when creating artifact " ++ artifact_outputname a
          ++ ":\n" ++ err
    where
    aux c = do
      putStrLn ("Artifact " ++ artifact_outputname a)
      putStrLn "================"
      T.putStrLn c
      putStrLn "================"
  
  