{-| Dictionary functions.

-}
module Shikensu.Contrib
    ( clearMetadata
    , clearMetadataDef
    , clone
    , copyPropsToMetadata
    , copyPropsToMetadataDef
    , exclude
    , insertMetadata
    , insertMetadataDef
    , permalink
    , permalinkDef
    , prefixDirname
    , prefixDirnameDef
    , rename
    , renameDef
    , renameExt
    , renameExtDef
    , renderContent
    , renderContentDef
    , replaceMetadata
    , replaceMetadataDef
    ) where

import Data.ByteString (ByteString)
import Flow
import Shikensu (forkDefinition)
import Shikensu.Internal.Utilities (cleanPath, compileParentPath, compilePathToRoot)
import Shikensu.Metadata (transposeToMetadata)
import Shikensu.Types
import System.FilePath (FilePath, combine)

import qualified Data.HashMap.Strict as HashMap (empty, union)


{-| Clear metadata.

Replace the current hash map with an empty one.
-}
clearMetadata :: Dictionary -> Dictionary
clearMetadata =
    fmap (clearMetadataDef)


clearMetadataDef :: Definition -> Definition
clearMetadataDef def =
    def { metadata = HashMap.empty }



{-| Clone.

For each definition that has the given `localPath` (1st argument),
make a clone with a new `localPath` (2nd argument),
and add that into dictionary just after the matching definition.

> clone "index.html" "200.html" dictionary
-}
clone :: FilePath -> FilePath -> Dictionary -> Dictionary
clone existingPath newPath dict =
    let
        makeNew = \def acc ->
            if (localPath def) == existingPath
                then acc ++ [forkDefinition newPath def]
                else acc
    in
        dict ++ (foldr makeNew [] dict)



{-| Copy definition properties into the metadata.

See the `toJSON` implementation for `Definition` in `Shikensu.Types`
to see what properties get put in here.
-}
copyPropsToMetadata :: Dictionary -> Dictionary
copyPropsToMetadata =
    fmap (copyPropsToMetadataDef)


copyPropsToMetadataDef :: Definition -> Definition
copyPropsToMetadataDef def =
    def
        { metadata = HashMap.union (transposeToMetadata def) (metadata def) }



{-| Exclude.

Filter out the definitions that have the given `localPath`.
-}
exclude :: FilePath -> Dictionary -> Dictionary
exclude path =
    filter (\def -> (localPath def) /= path)



{-| Insert metadata.

Merge the current hash map with another one.
-}
insertMetadata :: Metadata -> Dictionary -> Dictionary
insertMetadata a =
    fmap (insertMetadataDef a)


insertMetadataDef :: Metadata -> Definition -> Definition
insertMetadataDef given def =
    def { metadata = HashMap.union given (metadata def) }



{-| Permalink.

Append the basename to the dirname,
and change the basename to the given string.
It will NOT change definitions that already have the new basename.

> permalink "index" dictionary
-}
permalink :: String -> Dictionary -> Dictionary
permalink a =
    fmap (permalinkDef a)


permalinkDef :: String -> Definition -> Definition
permalinkDef newBasename def =
    if (basename def) /= newBasename then
        let
            newDirname = cleanPath $ combine (dirname def) (basename def)
        in
            def
                { basename    = newBasename
                , dirname     = newDirname
                , parentPath  = compileParentPath $ newDirname
                , pathToRoot  = compilePathToRoot $ newDirname
                }
    else
        def



{-| Prefix dirname.

Prefix the dirname of each definition with a given string.
-}
prefixDirname :: String -> Dictionary -> Dictionary
prefixDirname prefix = fmap (prefixDirnameDef prefix)


prefixDirnameDef :: String -> Definition -> Definition
prefixDirnameDef prefix def =
    let
        newDirname = prefix ++ (dirname def)
    in
        def
            { dirname     = newDirname
            , parentPath  = compileParentPath $ newDirname
            , pathToRoot  = compilePathToRoot $ newDirname
            }



{-| Rename.

Change the `localPath` of the definitions that match a given `localPath`.
For example, if you have a definition with the local path `a/b/example.html`:

> rename "a/b/example.html" "example/index.html" dictionary

See `Shikensu.localPath` for more info.
-}
rename :: FilePath -> FilePath -> Dictionary -> Dictionary
rename a b = fmap (renameDef a b)


renameDef :: FilePath -> FilePath -> Definition -> Definition
renameDef oldPath newPath def =
  if (localPath def) == oldPath
    then forkDefinition newPath def
    else def



{-| Rename extension.

Example:

> renameExt ".markdown" ".html" dictionary
> -- The definitions that had the extname ".markdown"
> -- now have the extname ".html"
-}
renameExt :: String -> String -> Dictionary -> Dictionary
renameExt a b =
    fmap (renameExtDef a b)


renameExtDef :: String -> String -> Definition -> Definition
renameExtDef oldExtname newExtname def =
    if (extname def) == oldExtname
        then def { extname = newExtname }
        else def



{-| Render content.

Replace the content property by providing a renderer.
A renderer is a function with the signature `Definition -> Maybe ByteString`.

You can use this to render templates, markdown, etc.
-}
renderContent :: (Definition -> Maybe ByteString) -> Dictionary -> Dictionary
renderContent a =
    fmap (renderContentDef a)


renderContentDef :: (Definition -> Maybe ByteString) -> Definition -> Definition
renderContentDef renderer def =
    def { content = renderer def }



{-| Replace metadata.

Replace the current hash map with another one.
-}
replaceMetadata :: Metadata -> Dictionary -> Dictionary
replaceMetadata a =
    fmap (replaceMetadataDef a)


replaceMetadataDef :: Metadata -> Definition -> Definition
replaceMetadataDef given def =
    def { metadata = given }