content: {-# LANGUAGE OverloadedStrings   #-}
  
  module Telemetry where
  
  import           Data.Aeson
  import qualified Data.ByteString         as BS
  import qualified Data.ByteString.Base64  as BSB64
  import qualified Data.ByteString.Lazy    as LBS
  import           Data.Semigroup          ((<>))
  import           Network.HTTP.Client     (getUri, httpLbs, method, newManager,
                                            parseRequest, queryString,
                                            requestHeaders, responseStatus, Response, Request, HttpException)
  import           Network.HTTP.Client.TLS (tlsManagerSettings)
  import           Network.HTTP.Types      (statusCode)
  import           Control.Concurrent
  import           System.Directory
  import           Data.UUID
  import           Data.UUID.V4
  import           Data.UUID.V1
  import           Control.Monad
  import           Data.Time.Clock
  import           Data.Time.ISO8601
  import           Control.Concurrent.STM.TVar
  import           Data.Maybe
  import qualified Data.Map.Strict as M
  import           System.Environment      (lookupEnv)
  import           System.FilePath         ((</>))
  
  import Foreign
  
  import Plugin.SimulaViewSprite
  import Plugin.Types
  
  import Control.Exception
  
  type SurfaceTelemetryMap = M.Map SimulaView GodotSimulaViewSprite -- C2HS version
  
  data Payload = Payload
                 { numWindowsOpen :: TVar SurfaceTelemetryMap
                 , minutesElapsedSinceLastPayload :: Double
                 , minutesTotalSession :: Double
                 }
  
  mixPanelToken = "d893bae9548d8e678cef251fd81df486" :: String
  
  getSimulaConfigDir :: IO FilePath
  getSimulaConfigDir = do
    maybeConfigHome <- lookupEnv "SIMULA_CONFIG_DIR"
    return $ case maybeConfigHome of
      Just path -> path
      Nothing -> "./.config"
  
  generateSessionUUID :: IO (UUID)
  generateSessionUUID = do
    configDir <- getSimulaConfigDir
    let uuidPath = configDir </> "UUID"
    createDirectoryIfMissing True configDir
    exists <- doesFileExist uuidPath
    maybeRandomUUID <- Data.UUID.V1.nextUUID
    let randomUUID = fromMaybe nil maybeRandomUUID
    let randomUUIDToString = Data.UUID.toString randomUUID
    writeFile uuidPath randomUUIDToString
    strUUID <- readFile uuidPath
    let maybeUUID = Data.UUID.fromString strUUID
    return $ case maybeUUID of
      (Just uuid) -> uuid
      Nothing     -> (nil :: UUID)
  
  ensureUUIDIsRegistered :: UUID -> IO ()
  ensureUUIDIsRegistered uuid = do
    let uuidStr = Data.UUID.toString uuid :: String
  
    -- Get the HTTP connection manager with default TLS settings.
    manager <- newManager tlsManagerSettings
    utcCurrentTime <- getCurrentTime
    let iso8601CurrentTimeStr = formatISO8601 utcCurrentTime
    -- print iso8601CurrentTimeStr
  
    -- Construct a bytestring encoded request object with the properties we want.
    let encodedRequestObject = LBS.toStrict (encode
          (object
              [ "$token" .= (mixPanelToken :: String)
              , "$distinct_id" .= (uuidStr :: String)
              , "$set_once" .= object [ "firstLoginDate" .= (iso8601CurrentTimeStr :: String)  -- QWERTY: iso-8601 string
                                      , "$name"            .= (uuidStr :: String)
                                      ]
              ]))
    -- 'mappend' the query parameter and base64 encode it.
    let requestQueryString = ("data=" <> BSB64.encode encodedRequestObject)
  
    -- Construct the 'Request'
    eitherErrorOrInitialRequest <- try $ parseRequest "http://api.mixpanel.com/engage/" :: IO (Either HttpException Request)
    case eitherErrorOrInitialRequest of
      Left error -> putStrLn "MixPanel parseRequest error!"
      Right initialRequest -> do let request = initialRequest
                                      { method = "POST"
                                      , queryString = requestQueryString
                                      , requestHeaders = [("Content-Type", "application/json; charset=utf-8")]
                                      }
                                 eitherErrorOrResponse <- try $ httpLbs request manager :: IO (Either HttpException (Response LBS.ByteString))
                                 case eitherErrorOrResponse of
                                   Left _ -> putStrLn "MixPanel httpLbs error!"
                                   Right _ -> return ()
    return ()
  
  forkSendAppLaunchEvent :: UUID -> IO ThreadId
  forkSendAppLaunchEvent uuid = forkIO $ do
    let uuidStr = Data.UUID.toString uuid :: String
  
    -- Get the HTTP connection manager with default TLS settings.
    manager <- newManager tlsManagerSettings
  
    -- Construct a bytestring encoded request object with the properties we want.
    let encodedRequestObject = LBS.toStrict (encode
          (object
              [ "event" .= ("App launched." :: String)
              , "properties"  .=  object [  "distinct_id" .= (uuidStr :: String)
                                         ,  "token"       .= (mixPanelToken :: String)
                                         ]
              ]))
    -- 'mappend' the query parameter and base64 encode it.
    let requestQueryString = ("data=" <> BSB64.encode encodedRequestObject)
  
    -- Construct the 'Request'
    eitherErrorOrInitialRequest <- try $ parseRequest "http://api.mixpanel.com/engage/" :: IO (Either HttpException Request)
    case eitherErrorOrInitialRequest of
      Left error -> putStrLn "MixPanel parseRequest error!"
      Right initialRequest -> do let request = initialRequest
                                      { method = "POST"
                                      , queryString = requestQueryString
                                      , requestHeaders = [("Content-Type", "application/json; charset=utf-8")]
                                      }
                                 eitherErrorOrResponse <- try $ httpLbs request manager :: IO (Either HttpException (Response LBS.ByteString))
                                 case eitherErrorOrResponse of
                                   Left _ -> putStrLn "MixPanel httpLbs error!"
                                   Right _ -> return ()
  
  forkSendPayloadEveryMinuteInterval :: UUID -> TVar SurfaceTelemetryMap -> Integer -> IO ThreadId
  forkSendPayloadEveryMinuteInterval uuid tvarSurfaceTelemetryMap min = forkIO $ (doLoop 0)
      where
      doLoop :: Double -> IO ()
      doLoop dbl = do
          threadDelay (1000 * 1000 * 60 * (fromIntegral min))
          forkSendPayload uuid (Payload { numWindowsOpen = tvarSurfaceTelemetryMap
                                        , minutesElapsedSinceLastPayload = (fromIntegral min)
                                        , minutesTotalSession = (dbl + (fromIntegral min))
                                        })
          putStrLn $ "minutesTotalSession: " ++ (show (dbl + (fromIntegral min)))
          doLoop (dbl + (fromIntegral min))
  
  forkSendPayload :: UUID -> Payload -> IO ()
  forkSendPayload uuid (Payload { numWindowsOpen = tvarSurfaceTelemetryMap
                                , minutesElapsedSinceLastPayload = minutesElapsedSinceLastPayload'
                                , minutesTotalSession = minutesTotalSession'
                                }) = do
    let uuidStr = Data.UUID.toString uuid :: String
  
    -- Get the HTTP connection manager with default TLS settings.
    manager <- newManager tlsManagerSettings
  
    --  readMvarInt <- readMVar mvarInt
    surfaceMap  <- readTVarIO tvarSurfaceTelemetryMap
    let numSurfaces = M.size surfaceMap
  
    -- Construct a bytestring encoded request object with the properties we want.
    let encodedRequestObject = LBS.toStrict (encode 
          (object
              [ "event" .= ("Payload" :: String)
              , "properties"  .=  object [  "distinct_id"  .= (uuidStr :: String)
                                         ,  "token"        .= (mixPanelToken :: String)
                                         ,  "minutesTotalSession" .= (minutesTotalSession' :: Double)
                                         ,  "minutesElapsedSinceLastPayload" .=    (minutesElapsedSinceLastPayload' :: Double)
                                         ,  "numWindowsOpen" .= (numSurfaces:: Int)
                                         ]
              ]))
  
    -- 'mappend' the query parameter and base64 encode it.
    let requestQueryString = ("data=" <> BSB64.encode encodedRequestObject)
  
    -- Construct the 'Request'
    initialRequest <- parseRequest "http://api.mixpanel.com/track/"
    let request = initialRequest
          { method = "POST"
          , queryString = requestQueryString
          , requestHeaders = [("Content-Type", "application/json; charset=utf-8")]
          }
  
    response <- httpLbs request manager
    return ()
  
  startTelemetry :: TVar SurfaceTelemetryMap -> IO ()
  startTelemetry tvarSurfaceTelemetryMap = do
    uuid <- generateSessionUUID
    forkIO $ ensureUUIDIsRegistered uuid
    forkSendPayloadEveryMinuteInterval uuid tvarSurfaceTelemetryMap 5 -- Send payload every 5 minutes
    return ()