{-# LANGUAGE MultiWayIf #-}
------------------------------------------------------------------------------
-- |
-- Module         : Library
-- Copyright      : (C) 2014 Samuli Thomasson
-- License        : BSD-style (see the file LICENSE)
-- Maintainer     : Samuli Thomasson <samuli.thomasson@paivola.fi>
-- Stability      : experimental
-- Portability    : non-portable
------------------------------------------------------------------------------
module Library where

import           ClassyPrelude
import           Database.Esqueleto
import           Control.Monad.Logger
import qualified Data.Text        as T
import           Data.Default
import           System.FilePath
import           System.Directory

import Types
import Core
import AskOption
import PrettyPrint
import Parser

-- | Search library for titles.
searchAnime :: [Text] -> M [TitleEntity]
searchAnime = db . selectTitles . const . fuzzyTitle

askLocalAnime :: [Text] -> AskConf (Entity Anime)
askLocalAnime parts = def
    { askTitle   = "Ambigous title, please select: "
    , askView    = prettyAnimeStatusTitle . entityVal
    , askFetch   = db . selectAnimeFuzzy . words
    , askInitial = Just (unwords parts)
    , askAfterwards = \query anime -> void . db .
        insertUnique $ Alt (animeTitle $ entityVal anime) query
    }

-- * Files

-- | Insert a list of files assigned to a title.
--
-- XXX: fails when the list contains multiple different titles
registerFilesTo :: [File] -> Entity Anime -> M ()
registerFilesTo []       _                  = return ()
registerFilesTo xs@(x:_) (Entity key anime) = do
    dir      <- db $ get' $ fileDir x
    inserted <- db $ mapM (insertUnique . (\p -> p{fileAnime = Just key})) files

    let eps   = map (fileEpisode . snd) . filter (isJust . fst) $ zip inserted files
        ppDir = brackets $ pretty $ dirParent dir </> dirBasename dir

    ppPutLn $ if null eps
            then pretty title <+> "up to date" <+> ppDir
            else "imported" <+> pretty title
                            <+> ranges' (sepEpisodes eps)
                            <+> brackets ("out of " <+> maybe "?" pretty (animeEpisodeCount anime))
                            <+> ppDir
    where
        files = sortBy (comparing fileEpisode) xs
        title = "`" <> fileTitle x <> "'"

-- ** Query

-- | Read files in a directory (Right) or the file (Left).
readFilesFrom :: FilePath -> M (Either File [File])
readFilesFrom fp = do
    absPath <- toAbsolute fp
    aFile   <- io $ doesFileExist absPath
    if aFile
        then either (fail . ("Could not read filename " <>) . show) (return . Left) =<< parseFileName absPath
        else liftM Right $ io (listDirectory absPath) >>= io . filterM doesFileExist >>= mapM parseFileName >>= eitherFiles

-- ** Move

-- | Move given files to destination given on command line or to default
-- destination specified in the configuration.
move :: [FilePath] -> M ()
move targets = do
    to <- liftM (</> "") . toAbsolute =<< asks (fromMaybe <$> coAnimeDir <*> fmap unpack . coMoveDestination)
    mapM_ (moveTarget to) targets

-- | "moveTarget dst src". Both arguments are assumed absolute. Destination
-- is assumed to have a trailing slash.
moveTarget :: FilePath -> FilePath -> M ()
moveTarget toParent x = do
    isdir <- io $ doesDirectoryExist x
    if isdir
        then mvDir (x </> "")
        else do isfile <- io $ doesFileExist x
                if isfile
                    then mvFile x
                    else logWarnN $ "File not found: " <> pack x
    where
        mvDir target = do
            let target'      = toParent </> targetBase
                targetBase   = takeBaseName $ takeDirectory target
                targetParent = takeDirectory target

            rename' target target' $
                db $ update $ \dir -> do
                    set dir [ DirBasename =. val targetBase, DirParent =. val toParent ]
                    where_ $ dir ^. DirParent ==. val targetParent
                        &&. dir ^. DirBasename ==. val targetBase

        mvFile target = do
            Right file <- parseFileName target
            let targetDir = toParent  </> unpack (T.replace "/" "_" $ fileTitle file) </> ""
                target'   = targetDir </> fileBasename file

            io $ createDirectoryIfMissing True targetDir
            rename' target target' $ db $ do
                did <- resolveDirDb targetDir
                update $ \f -> do
                    set f [ FileDir =. val did ]
                    where_ $ f ^. FileDir ==. val (fileDir file)

        rename' target target' m = do
            isdir <- io $ doesDirectoryExist target'
            isfile <- io $ doesFileExist target'
            if | isdir -> io (renameDirectory target target') >> m
               | isfile ->  io (renameFile target target') >> m
               | otherwise -> logWarnN $ "The target `" <> pack target' <> "` already exists"
