module Sound.Jammit.Internal.ImageMagick
( connectVertical
, splitVertical
, joinPages
) where

import Control.Applicative ((<$>))
import Control.Monad (void)
import Data.Char (isDigit)
import Data.List (isPrefixOf, sortBy)
import Data.Maybe (listToMaybe)
import Data.Ord (comparing)
import System.Environment (lookupEnv)
import System.Exit (ExitCode(..))
import qualified System.Info as Info

import System.Directory (getDirectoryContents)
import System.FilePath ((</>))
import System.IO.Temp (createTempDirectory)
import System.Process (readProcess, readProcessWithExitCode)

import Sound.Jammit.Internal.TempFile

-- | Find an ImageMagick binary, because the names are way too generic, and
-- "convert" is both an ImageMagick program and a Windows built-in utility.
imageMagick :: String -> IO (Maybe String)
imageMagick cmd = do
  (code, _, _) <- readProcessWithExitCode cmd ["-version"] ""
  case code of
    ExitSuccess -> return $ Just cmd
    _ -> case Info.os of
      "mingw32" -> firstJustM $
        -- env variables for different configs of (ghc arch)/(imagemagick arch)
        -- ProgramFiles: 32/32 or 64/64
        -- ProgramFiles(x86): 64/32
        -- ProgramW6432: 32/64
        flip map ["ProgramFiles", "ProgramFiles(x86)", "ProgramW6432"] $ \env ->
          lookupEnv env >>= \var -> case var of
            Nothing -> return Nothing
            Just pf
              ->  fmap (\im -> pf </> im </> cmd)
              .   listToMaybe
              .   filter ("ImageMagick" `isPrefixOf`)
              <$> getDirectoryContents pf
      _ -> return Nothing

imageMagick' :: String -> IO String
imageMagick' cmd = imageMagick cmd >>= \ms -> case ms of
  Nothing -> error $ "imageMagick': couldn't find program " ++ cmd
  Just s  -> return s

-- | Only runs actions until the first that gives 'Just'.
firstJustM :: (Monad m) => [m (Maybe a)] -> m (Maybe a)
firstJustM [] = return Nothing
firstJustM (mx : xs) = mx >>= \x -> case x of
  Nothing -> firstJustM xs
  Just y  -> return $ Just y

-- | Stick images together vertically into one long image.
connectVertical :: [FilePath] -> TempIO FilePath
connectVertical [fin] = return fin
connectVertical fins = do
  cmd  <- liftIO $ imageMagick' "montage"
  fout <- newTempFile "connectVertical.png"
  void $ liftIO $ readProcess cmd
    (["-geometry", "100%", "-tile", "1x"] ++ fins ++ [fout]) ""
  return fout

-- | Splits an image vertically into chunks of a given height.
splitVertical :: Integer -> FilePath -> TempIO [FilePath]
splitVertical i fin = do
  cmd      <- liftIO $ imageMagick' "convert"
  tempdir  <- ask
  splitdir <- liftIO $ createTempDirectory tempdir "splitVertical"
  void $ liftIO $
    readProcess cmd ["-crop", "x" ++ show i, fin, splitdir </> "x.png"] ""
  map (splitdir </>) . sortBy (comparing getNumber) . filter isFile
    <$> liftIO (getDirectoryContents splitdir)
  where getNumber :: String -> Integer
        getNumber = read . takeWhile isDigit . dropWhile (not . isDigit)
        isFile = (`notElem` [".", ".."])

-- | Joins several images into a PDF, where each image is a page.
joinPages :: [FilePath] -> TempIO FilePath
joinPages fins = do
  cmd  <- liftIO $ imageMagick' "convert"
  fout <- newTempFile "joinPages.pdf"
  void $ liftIO $ readProcess cmd (fins ++ [fout]) ""
  return fout