{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}

module GetContents where

import System.IO hiding                             (getContents)
import Prelude hiding                               (truncate, getContents)
import Data.Text                                    (Text, pack, unpack)
import Data.Vector                                  (toList)
import Data.ByteString.Base64                       (decodeLenient)
import qualified Data.ByteString.Char8 as BS        (unpack, pack)
import qualified GitHub.Endpoints.Repos as Github
import GitHub.Auth

import Data.Maybe                                   (isNothing)
import Control.Monad                                (guard, when)
import Control.Monad.Trans.Class                    (lift)
import Control.Monad.IO.Class                       (liftIO)
import Data.String                                  (fromString)
import System.Environment                           (lookupEnv)
import System.Directory                             (createDirectoryIfMissing)
import System.FilePath.Posix                        (dropFileName, (</>))
import Control.Exception.Lifted
import Control.Monad.Trans.Except
import Control.Monad.Trans.List
import Data.EitherR                                 (fmapL)
import Data.Either                                  (partitionEithers)
import Data.Either.Utils                            (maybeToEither)

tokenName = "GITHUB_DUOMEMO_TOKEN"

getAuth :: IO (Maybe (Auth))
getAuth = do
    token <- lookupEnv tokenName
    return $ OAuth . fromString <$> token
    
-- ExceptT :: m (Either e a) -> ExceptT e m a
-- ListT :: m [a] -> ListT m a
-- ExceptT String (ListT IO) (String, String) => (ListT IO) (Either String (String, String)) => IO [Either String (String, String)]
-- ListT (ExceptT String IO) (String, String) => (ExceptT String IO) [(String, String)] => IO Either String [(String, String)]

-- transforms to IO [Either String (String, String)]
getContents :: String -> ExceptT String (ListT IO) (String, String)
getContents gitPath = 
    do
        -- TODO: Find out why the semantics for auth below returning auth::Auth is not equivalent to the simple liftIO $ maybeToEither which returns Either String Auth
        (auth::Auth) <- 
            -- ::ExceptT String (ListT IO) Auth            
            ExceptT . ListT $ 
                -- ::IO [Either String Auth]
                (:[]) <$> 
                    -- maybeToEither :: IO (Either String Auth)
                    maybeToEither ("Failed to find GIT access token.\nCheck the sytem environment variables and make sure " ++ tokenName ++ " variable contains a valid token. Otherwise generate it here https://github.com/settings/tokens/new")
                        <$> getAuth
        (content::Github.Content)
            -- TODO: Why can't i use monadic return or lift. 
            -- TODO: Study lift again. Search for the possibility of monad decomposition
            <- ExceptT . ListT $
                -- (:[]) :: IO [Either String Github.Content]
                (:[]) <$> 
                    -- fmapL :: IO (Either String Github.Content)
                    fmapL (show :: Github.Error -> String) <$> Github.contentsFor' (Just auth) "erithion" "temp_test" gitPath Nothing
        readItem content
        

readItem :: Github.Content -> ExceptT String (ListT IO) (String, String)
readItem (Github.ContentFile fileData) =
    do 
        let contentInfo = Github.contentFileInfo fileData
            fileName = unpack $ Github.contentPath contentInfo
            encoding = unpack $ Github.contentFileEncoding fileData
            file = unpack $ Github.contentFileContent fileData
            output = BS.unpack ( decodeLenient ( BS.pack file ))
        if encoding == "base64" 
        then ExceptT . ListT . return $ 
                [Right (fileName, output)]
        else ExceptT . ListT . return $ 
                [Left $ "Unsupported encoding " ++ encoding ++ "\nfor " ++ unpack (Github.contentUrl contentInfo)]

readItem (Github.ContentDirectory items) =
    do
        let (names::[String]) = map (unpack . Github.contentPath . Github.contentItemInfo) $ toList items
        name <- ExceptT . ListT . return $ Right <$> names
        getContents name
        
writeUTF8File :: String -> (String, String) -> ExceptT String IO ()
writeUTF8File saveTo (fileName, fileData) = ExceptT $
    -- tryJust :: IO (Either String String)
    -- catching and rethrowing IOExceptions as String
    tryJust (return . show :: IOException -> Maybe String) $ 
        do 
            let filePath = saveTo </> fileName
            createDirectoryIfMissing True (dropFileName filePath)
            withBinaryFile filePath WriteMode $ \handle->
                do
                    hPutStr handle fileData

main = do
  putStrLn "Root"
  putStrLn "===="
  files <- runListT $ runExceptT $ 
      do 
        dat <- getContents ""
        let (write::IO (Either String ())) = runExceptT . writeUTF8File ".\\data" $ dat
        result <- ExceptT . ListT $ (:[]) <$> write
        return dat

  let (lefts, rights) = partitionEithers files
  putStrLn "ERRORS\n\n\n\n"
  putStrLn . unlines $ lefts
  return ()

--writeUTF8FileError :: (String, String) -> ExceptT String IO ()
--writeUTF8FileError (fileName, fileData) = ExceptT . return . Left $ "I generated error"

