content: {-# LANGUAGE DataKinds     #-}
  {-# LANGUAGE TypeOperators #-}
  {-# LANGUAGE ScopedTypeVariables #-}
  
  -- | This is where it all started
  module Cut.Crap
    ( entryPoint
    , combineDir
    , makeSrt
    , runListenCut
    , runEdit
    , runGenSubs
    )
  where
  
  import           Cut.Shell
  import           Control.Lens
  import           Control.Monad
  import           Control.Monad.Catch
  import           Control.Monad.IO.Class
  import           Control.Monad.IO.Unlift
  import           Cut.Analyze
  import           Cut.CutVideo
  import           Cut.Options
  import           Cut.SpeechRecognition
  import           Data.Foldable                (fold, foldl', traverse_)
  import           Data.Generics.Product.Fields
  import           Data.Maybe
  import           Data.Text                    (Text)
  import qualified Data.Text                    as Text
  import qualified Data.Text.IO                 as T
  import           Data.Text.Lens
  import           Data.Time
  import           GHC.Generics                 hiding (to)
  import           Options.Applicative
  import           Shelly                       hiding (FilePath, shelly)
  import           System.IO.Temp
  import Cut.Download
  import Paths_cut_the_crap (version)
  import Data.Version (showVersion)
  import Text.Printf
  
  -- | reads settings from terminal and runs whatever command was
  --   given in program options
  entryPoint :: MonadMask m => MonadUnliftIO m => m ()
  entryPoint = do
    result <- liftIO readSettings
    case  result of
      ListenCut cut -> downloadCutifNeccisary cut $ void . runListenCut
      GenerateSubtitles x -> downloadIfNeccisary x runGenSubs
  
  runGenSubs :: MonadIO m => FileIO FilePath -> m ()
  runGenSubs options = liftIO $ withTempDir options $ \tmp -> do
      downloadCutifNeccisary simpleOptions $ \betterOptions -> do
        result <- shelly $ detectSpeech (set voice_track 1 betterOptions) tmp $ options ^. in_file
        print result
        traverse_ (T.writeFile (options ^. out_file) . makeSrt) result
  
  -- | Runs cut-the-crap with provided `ListenCutOptions`
  runListenCut :: MonadMask m => MonadUnliftIO m => ListenCutOptions -> m [Interval Sound]
  runListenCut options = do
    liftIO $ putStr "started with options: "
    liftIO $ print options
  
    -- first figure out what's up in the vid
    parsed <- detectSoundInterval options
  
    -- then do stuff to it
    parsed <$ case parsed of
      [] ->
        liftIO
          $ putStr
              "\n\nNo silence in input video detected. There is nothing to be cut so exiting.\n\n"
      _ -> liftIO $ withTempDir (options ^. lc_fileio) $ runEdit options parsed
  
  withTempDir :: FileIO FilePath -> (FilePath -> IO ()) -> IO ()
  withTempDir filioOpts fun =
    maybe (withTempDirectory "/tmp" "streamedit" fun) fun $ filioOpts ^. work_dir
  
  -- | Run editing on video from options with preprovided detections
  --   normally aquired throug `detect`
  runEdit :: ListenCutOptions -> [Interval Sound] -> FilePath -> IO ()
  runEdit options parsed tempDir = do
    extract options tempDir parsed
    shelly $ do
      combineDir options tempDir parsed
      getMusic options tempDir
  
  readSettings :: IO (ProgramOptions InputSource)
  readSettings = customExecParser (prefs showHelpOnError) $ info
    (helper <*> parseProgram)
    (fullDesc <> header (printf "Cut the crap %s" curretVersion) <> progDesc
      "Automated video extracting, can cut out silences"
    )
  
  
  curretVersion :: String
  curretVersion = showVersion version
  
  musicFile :: FilePath
  musicFile = "music.mp3"
  
  withMusicFile :: FilePath
  withMusicFile = "combined.mkv"
  
  getMusic :: ListenCutOptions -> FilePath -> Sh ()
  getMusic opt' tempDir = do
    res <- case opt' ^. music_track of
      Nothing -> pure $ Text.pack combinedFile
      Just x  -> do
        extractMusicTrack x (opt' ^. lc_fileio . in_file) tempDir
        mergeMusicAndVideo tempDir
        echo "done getting music"
        pure $ Text.pack (tempDir </> withMusicFile)
    workdir <- pwd
    echo "---"
    echo $ Text.pack $  printf "writing '%s' to '%s' in working dir '%s'" res outfile workdir
    when (isOutRelative && workdir == "/") $ echo "If this working dir is wrong try an absolute path as OUTPUT_FILE"
    cp (fromText res) (fromText $ Text.pack outfile)
    where
      combinedFile = tempDir </> combineOutput
      outfile :: FilePath
      outfile = opt' ^. lc_fileio . out_file
  
      isOutRelative = take 1 outfile /= "/"
  
  
  extractMusicTrack :: Int -> FilePath -> FilePath -> Sh ()
  extractMusicTrack musicTrack inputFile tempDir = void $ ffmpeg inputFile args
   where -- https://stackoverflow.com/questions/7333232/how-to-concatenate-two-mp4-files-using-ffmpeg
    args =
      [ "-map"
      , "0:" <> Text.pack (show musicTrack)
      , Text.pack (tempDir <> "/" <> musicFile)
      ]
  
  mergeMusicAndVideo :: FilePath -> Sh ()
  mergeMusicAndVideo tempDir = void $ ffmpeg' args
   where -- https://stackoverflow.com/questions/7333232/how-to-concatenate-two-mp4-files-using-ffmpeg
    args =
      [ "-i"
      , Text.pack $ tempDir <> "/" <> combineOutput
      , "-i"
      , Text.pack $ tempDir <> "/" <> musicFile
      , "-filter_complex"
      , "[0:a][1:a]amerge=inputs=2[a]"
      , "-map"
      , "0:v"
      , "-map"
      , "[a]"
      , "-c:v"
      , "copy"
      , "-c:a"
      , "mp3"
      , "-ac"
      , "2"
      , "-shortest"
      , Text.pack (tempDir <> "/" <> withMusicFile)
      ]
  
  
  data SrtDisplay = SrtDisplay
    { _srt_from     :: DiffTime
    , _srt_to       :: DiffTime
    , _srt_words    :: Text
    , _srt_position :: Int
    } deriving (Show, Eq, Generic)
  
  instance Semigroup SrtDisplay where
    (<>) a b = SrtDisplay{
      _srt_from = a ^. srt_from,
      _srt_to = b ^. srt_to,
      _srt_words = a ^. srt_words <> " " <> b ^. srt_words,
      _srt_position = a ^. srt_position
      }
  instance Monoid SrtDisplay where
    mempty = SrtDisplay{
      _srt_from = 0,
      _srt_to = 0,
      _srt_words = mempty,
      _srt_position = 0
      }
  
  srt_from :: Lens' SrtDisplay DiffTime
  srt_from = field @"_srt_from"
  srt_to :: Lens' SrtDisplay DiffTime
  srt_to = field @"_srt_to"
  srt_words :: Lens' SrtDisplay Text
  srt_words = field @"_srt_words"
  srt_position :: Lens' SrtDisplay Int
  srt_position = field @"_srt_position"
  
  makeSrt :: [WordFrame] -> Text.Text
  makeSrt frames = fold $ fmap (formatSrt . foldl' (<>) mempty) $ groupBySentence $ imap (toSrtDisplay off) frames
    where off = fromMaybe noOffset $ frames ^? ix 0 . frame_from
  
  groupBySentence :: [SrtDisplay] -> [[SrtDisplay]]
  groupBySentence = snd . foldl' innerFold ([], []) -- face
  
  innerFold :: ([SrtDisplay], [[SrtDisplay]]) -> SrtDisplay ->  ([SrtDisplay], [[SrtDisplay]])
  innerFold (prev, res) x = if x ^. srt_words == "<sil>" then ([], prev : res) else
    (x : prev, res)
  
  
  toSrtDisplay :: FrameOffset -> Int -> WordFrame -> SrtDisplay
  toSrtDisplay firstOffset ix' frame = SrtDisplay
    { _srt_from     = frame ^. frame_from . to (toDiffTime firstOffset)
    , _srt_to       = frame ^. frame_to . to (toDiffTime firstOffset)
    , _srt_words    = frame ^. frame_word
    , _srt_position = ix'
    }
  
  
  -- | wikipedia explains the srt format pretty well: https://en.wikipedia.org/wiki/SubRip
  --  in escence :
  -- [A numeric counter identifying each sequential subtitle]
  -- [The time that the subtitle should appear on the screen] --–> [d the time it should disappear]
  -- [Subtitle text itself on one or more lines]
  -- [A blank line containing no text, indicating the end of this subtitle]
  formatSrt :: SrtDisplay -> Text.Text
  formatSrt sentence = fold
    [ sentence ^. srt_position . to show . packed
    , "\n"
    , Text.pack
    $  formatTime defaultTimeLocale "%0H:%0M:%0S,000"
    $  sentence
    ^. srt_from
    , " --> "
    , Text.pack
    $  formatTime defaultTimeLocale "%0H:%0M:%0S,000"
    $  sentence
    ^. srt_to
    , "\n"
    , (sentence ^. srt_words)
    , "\n"
    , "\n"
    ]
  