{-# LANGUAGE NoImplicitPrelude #-}

-- | Description: interpret flags parsed by "IHaskell.Flags"
module IHaskell.Convert.Args (ConvertSpec(..), fromJustConvertSpec, toConvertSpec) where

import           IHaskellPrelude
import qualified Data.Text.Lazy as LT

import           Data.Functor.Identity (Identity(Identity))
import           Data.Char (toLower)
import           IHaskell.Flags (Argument(..), LhsStyle, lhsStyleBird, NotebookFormat(..))
import           System.FilePath ((<.>), dropExtension, takeExtension)

-- | ConvertSpec is the accumulator for command line arguments
data ConvertSpec f =
       ConvertSpec
         { convertToIpynb :: f Bool
         , convertInput :: f FilePath
         , convertOutput :: f FilePath
         , convertLhsStyle :: f (LhsStyle LT.Text)
         , convertOverwriteFiles :: Bool
         }

-- | Convert a possibly-incomplete specification for what to convert into one which can be executed.
-- Calls error when data is missing.
fromJustConvertSpec :: ConvertSpec Maybe -> ConvertSpec Identity
fromJustConvertSpec convertSpec = convertSpec
  { convertToIpynb = Identity toIpynb
  , convertInput = Identity inputFile
  , convertOutput = Identity outputFile
  , convertLhsStyle = Identity $ fromMaybe (LT.pack <$> lhsStyleBird) (convertLhsStyle convertSpec)
  }
  where
    toIpynb = fromMaybe (error "fromJustConvertSpec: direction for conversion unknown")
                (convertToIpynb convertSpec)
    (inputFile, outputFile) =
      case (convertInput convertSpec, convertOutput convertSpec) of
        (Nothing, Nothing) -> error "fromJustConvertSpec: no files specified for conversion"
        (Just i, Nothing)
          | toIpynb -> (i, dropExtension i <.> "ipynb")
          | otherwise -> (i, dropExtension i <.> "lhs")
        (Nothing, Just o)
          | toIpynb -> (dropExtension o <.> "lhs", o)
          | otherwise -> (dropExtension o <.> "ipynb", o)
        (Just i, Just o) -> (i, o)

-- | Does this @Argument@ explicitly request a file format?
isFormatSpec :: Argument -> Bool
isFormatSpec (ConvertToFormat _) = True
isFormatSpec (ConvertFromFormat _) = True
isFormatSpec _ = False

toConvertSpec :: [Argument] -> ConvertSpec Maybe
toConvertSpec args = mergeArgs otherArgs (mergeArgs formatSpecArgs initialConvertSpec)
  where
    (formatSpecArgs, otherArgs) = partition isFormatSpec args
    initialConvertSpec = ConvertSpec Nothing Nothing Nothing Nothing False

mergeArgs :: [Argument] -> ConvertSpec Maybe -> ConvertSpec Maybe
mergeArgs args initialConvertSpec = foldr mergeArg initialConvertSpec args

mergeArg :: Argument -> ConvertSpec Maybe -> ConvertSpec Maybe
mergeArg OverwriteFiles convertSpec = convertSpec { convertOverwriteFiles = True }
mergeArg (ConvertLhsStyle lhsStyle) convertSpec
  | Just previousLhsStyle <- convertLhsStyle convertSpec,
    previousLhsStyle /= fmap LT.pack lhsStyle
  = error $ printf "Conflicting lhs styles requested: <%s> and <%s>" (show lhsStyle)
              (show previousLhsStyle)
  | otherwise = convertSpec { convertLhsStyle = Just (LT.pack <$> lhsStyle) }
mergeArg (ConvertFrom inputFile) convertSpec
  | Just previousInputFile <- convertInput convertSpec,
    previousInputFile /= inputFile
  = error $ printf "Multiple input files specified: <%s> and <%s>" inputFile previousInputFile
  | otherwise = convertSpec
      { convertInput = Just inputFile
      , convertToIpynb = case (convertToIpynb convertSpec, fromExt inputFile) of
        (prev, Nothing)    -> prev
        (prev@(Just _), _) -> prev
        (Nothing, format)  -> fmap (== LhsMarkdown) format
      }
mergeArg (ConvertTo outputFile) convertSpec
  | Just previousOutputFile <- convertOutput convertSpec,
    previousOutputFile /= outputFile
  = error $ printf "Multiple output files specified: <%s> and <%s>" outputFile previousOutputFile
  | otherwise = convertSpec
      { convertOutput = Just outputFile
      , convertToIpynb = case (convertToIpynb convertSpec, fromExt outputFile) of
        (prev, Nothing)    -> prev
        (prev@(Just _), _) -> prev
        (Nothing, format)  -> fmap (== IpynbFile) format
      }
mergeArg (ConvertToFormat format) convertSpec = case format of
  LhsMarkdown -> convertSpec { convertToIpynb = Just False }
  IpynbFile -> convertSpec { convertToIpynb = Just True }
mergeArg (ConvertFromFormat format) convertSpec = case format of
  LhsMarkdown -> convertSpec { convertToIpynb = Just True }
  IpynbFile -> convertSpec { convertToIpynb = Just False }
mergeArg unexpectedArg _ = error $ "IHaskell.Convert.mergeArg: impossible argument: "
                                   ++ show unexpectedArg

-- | Guess the format based on the file extension.
fromExt :: FilePath -> Maybe NotebookFormat
fromExt s =
  case map toLower (takeExtension s) of
    ".lhs"   -> Just LhsMarkdown
    ".ipynb" -> Just IpynbFile
    _        -> Nothing
