module Dingo.Internal.ResourceBundle.Internal
       ( ResourceBundle
       , ResourceBundleSet
       , ResourceDirectory
       , makeResourceBundle
       , elemResourceBundleSet
       , embedDir
       , findResourceInBundleSet
       , getResourceBundleContents
       , resourceBundleSetFromList
       ) where

import           Data.ByteString (ByteString)
import qualified Data.ByteString.Lazy as BSL
import           Data.Digest.Pure.SHA (sha256, showDigest)
import           Data.FileEmbed
import qualified Data.Label as L
import           Data.HashMap.Strict (HashMap)
import qualified Data.HashMap.Strict as H
import           Data.Monoid (Monoid(..))
import           Data.Text (Text)
import qualified Data.Text as T
import qualified Data.Text.Lazy as TL
import qualified Data.Text.Lazy.Encoding as TLE

-- | Type alias for convience for using 'embedDir'.
type ResourceDirectory = [(String, ByteString)]

-- | Resource bundle data structure.
data ResourceBundle =
  ResourceBundle { _rbGUID :: Text
                 , _rbDirectory :: HashMap Text ByteString
                 }

$(L.mkLabels [''ResourceBundle])

-- | Map of resource bundles.
newtype ResourceBundleSet = ResourceBundleSet (HashMap Text ResourceBundle)

-- | Make a resource bundle from an embedded directory structure returned by 'embedDir'.
makeResourceBundle :: ResourceDirectory -> ResourceBundle
makeResourceBundle resourceDirectory =
  ResourceBundle guid resourceDirectory'
  where
    guid =
      T.pack $ showDigest $ sha256 $ BSL.concat $
      map (\(p,c) -> BSL.concat [ TLE.encodeUtf8 $ TL.pack p, BSL.fromChunks [c]]) $
      resourceDirectory
    resourceDirectory' =
      H.fromList $ map convertFilePath resourceDirectory
    convertFilePath (p,c) =
      (T.pack p, c)

-- Retrieve resource bundle contents.
getResourceBundleContents :: ResourceBundle -> (Text, [(Text,ByteString)])
getResourceBundleContents (ResourceBundle guid directory) =
  (guid, H.toList directory)

-- Create resource bundles structure from a list.
resourceBundleSetFromList :: [ResourceBundle] -> ResourceBundleSet
resourceBundleSetFromList =
  ResourceBundleSet . H.fromList . map (\i -> (L.get rbGUID i, i))

-- Find a path within a resource bundle.
findResource :: ResourceBundle -> Text -> Maybe ByteString
findResource bundle path =
  H.lookup path $ L.get rbDirectory bundle

-- Resource bundle set is a monoid.
instance Monoid ResourceBundleSet where
  mempty = ResourceBundleSet H.empty
  mappend (ResourceBundleSet a) (ResourceBundleSet b) = ResourceBundleSet (a `mappend` b)

-- Find a resource in a set of bundles.
findResourceInBundleSet ::  Text -> [Text] -> ResourceBundleSet -> Maybe ByteString
findResourceInBundleSet bundleId path (ResourceBundleSet resourceBundles) =
  case H.lookup bundleId resourceBundles of
    Nothing -> Nothing
    Just resourceBundle -> findResource resourceBundle path'
  where
    path' :: Text
    path' = T.intercalate "/" path

-- Check if a resource bundle exists in a resource bundle set.
elemResourceBundleSet :: ResourceBundle -> ResourceBundleSet -> Bool
elemResourceBundleSet x (ResourceBundleSet xs) =
  case H.lookup (L.get rbGUID x) xs of
    Nothing -> False
    Just _ -> True