{-# LANGUAGE DataKinds        #-}
{-# LANGUAGE TypeOperators    #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE DeriveAnyClass #-}

-- | This entire module leaks like crazy, it's not a big deal because
--   at the moment this process is confined
module Cut.SpeechRecognition(speechAnalyses
                            , frame_from
                            , frame_to
                            , frame_word
                            , FrameOffset
                            , WordFrame
                            , ResultCode(..)
                            , toDiffTime
                            , toFrameOffset
                            , noOffset 
                            ) where

import Data.Time
import           GHC.Generics
import Data.Text(Text)
import qualified Data.Text as Text
import Foreign
import Foreign.C
import Control.Lens
import           Data.Generics.Product.Fields

#include "speech_recognition.h"

{#enum result_code as ResultCode {underscoreToCase}#}
deriving instance Show ResultCode

newtype FrameOffset = FrameOffset Int
  deriving (Generic, Show)

noOffset :: FrameOffset
noOffset = FrameOffset 0


toFrameOffset :: Int -> FrameOffset
toFrameOffset = FrameOffset

data WordFrame = WordFrame
  { _frame_from :: FrameOffset 
  , _frame_to :: FrameOffset 
  , _frame_word :: Text
  } deriving (Generic, Show)

instance Storable WordFrame where
        alignment _ = 4
        sizeOf _    = 16
        peek ptr    =
            WordFrame
              <$> (FrameOffset . toInt <$> peekByteOff ptr 0)
              <*> (FrameOffset . toInt <$> peekByteOff ptr 4)
              <*> (peekByteOff ptr 8 >>= fmap Text.pack . peekCString)
        poke ptr (WordFrame (FrameOffset d) (FrameOffset c) i) = do
            pokeByteOff ptr 0 d
            pokeByteOff ptr 4 c
            withCString (Text.unpack i) $ pokeByteOff ptr 8

frame_from :: Lens' WordFrame FrameOffset 
frame_from = field @"_frame_from"

frame_to :: Lens' WordFrame FrameOffset 
frame_to = field @"_frame_to"

toDiffTime :: FrameOffset -> FrameOffset -> DiffTime
toDiffTime (FrameOffset startoffset) (FrameOffset x) =
  secondsToDiffTime $ toInteger $ div (x - startoffset) 45

frame_word :: Lens' WordFrame Text
frame_word = field @"_frame_word"

toInt :: CInt -> Int
toInt = fromInteger . toInteger

detectedToList :: CInt -> Ptr WordFrame -> IO [WordFrame]
detectedToList c_used frames  =
  traverse (peek . plusPtr frames . (*) (sizeOf (undefined :: WordFrame)) ) [0..(used - 1)]
  where
    used = toInt c_used
    
-- | given a raw audio file, we can detect words
speechAnalyses :: FilePath -> IO (Either ResultCode [WordFrame])
speechAnalyses filePath = do
  c_result <- withCString filePath detect_words_ffi
  c_words <- peekByteOff c_result 8 
  c_words_used <- peekByteOff c_result 16 
  status_code <- toEnum . fromIntegral <$> {#get detect_result.code #} c_result
  case status_code of
    Success -> Right <$> detectedToList c_words_used c_words
    x -> pure $ Left x
    
foreign import ccall "detect_words" detect_words_ffi :: CString -> IO ({#type detect_result #}) 
