{-# LANGUAGE OverloadedStrings #-}
-- | Low-level descriptors: https://essentia.upf.edu/documentation/streaming_extractor_music.html#low-level .
module AcousticBrainz.LowLevel where

import           AcousticBrainz.StatisticalUnits
import           AcousticBrainz.MetaData
import qualified AcousticBrainz.LowLevel.Algorithm.SilenceRate as SilenceRate
import           AcousticBrainz.LowLevel.BarkBands
import           AcousticBrainz.LowLevel.CepstralCoefficients
import qualified AcousticBrainz.LowLevel.EquivalentRectangularBandwidth as EquivalentRectangularBandwidth
import           AcousticBrainz.LowLevel.MelBands
import           AcousticBrainz.LowLevel.Rhythm
import           AcousticBrainz.LowLevel.Spectral
import           AcousticBrainz.LowLevel.Tonal
import           MusicBrainz

import           Control.Monad.Catch
import           Data.Aeson                     as JSON
import           Data.Aeson.Types               as JSON
import qualified Data.ByteString.Lazy           as Lazy
import qualified Data.ByteString.Streaming      as Q
import           Data.ByteString.Streaming.HTTP
import           Data.Scientific
import qualified Data.Text                      as Text

-- | Call the following endpoint: https://acousticbrainz.readthedocs.io/api.html#get--api-v1-(uuid-mbid)-low-level .
getLowLevelData :: MonadIO m => MonadThrow m => MusicBrainzIdentifier -> m LowLevelResponse
getLowLevelData (MusicBrainzIdentifier identifier) = io $ do
  request <- parseRequest $ "https://acousticbrainz.org/api/v1/" <> Text.unpack identifier <> "/low-level"
  manager <- newManager tlsManagerSettings
  withHTTP request manager $ \r ->
    r & responseBody & Q.toLazy_ <&> eitherDecode >>= liftEither
  where liftEither (Left e) = throwM $ LowLevelException e
        liftEither (Right a) = return a

data LowLevelResponse = LowLevelResponse
  { _data     :: LowLevelData
  , _tonal    :: Tonal
  , _rhythm   :: Rhythm
  , _metadata :: MetaData
  } deriving(Eq, Read, Show)

instance FromJSON LowLevelResponse where
  parseJSON = withObject "response" $ \v -> LowLevelResponse
    <$> v .: "lowlevel"
    <*> v .: "tonal"
    <*> v .: "rhythm"
    <*> (v .: "metadata" >>= parseMetaData)


parseMetaData = withObject "metadata" $ \v -> MetaData
  <$> v .: "audio_properties"
  <*> v .: "tags"
  <*> v .: "version"


data LowLevelData = LowLevelData
  { _averageLoudness :: Scientific
  , _barkBands :: BarkBands
  , _dissonance :: StatisticalUnits
  , _dynamicComplexity :: Scientific
  , _equivalentRectangularBandwidthBands :: EquivalentRectangularBandwidth.Bands
  , _gfcc :: CepstralCoefficients
  , _highFrequencyContent :: StatisticalUnits
  , _melBands :: MelBands
  , _mfcc :: CepstralCoefficients
  , _pitchSalience :: StatisticalUnits
  , _silenceRate :: SilenceRate.Output
  , _spectral :: Spectral
  , _zeroCrossingRate :: StatisticalUnits
  } deriving(Eq, Ord, Read, Show)

instance FromJSON LowLevelData where
  parseJSON value = flip (withObject "lowlevel") value $ \v -> LowLevelData
    <$> v .: "average_loudness"
    <*> parseJSON value
    <*> v .: "dissonance"
    <*> v .: "dynamic_complexity"
    <*> parseJSON value
    <*> v .: "gfcc"
    <*> v .: "hfc"
    <*> parseJSON value
    <*> v .: "mfcc"
    <*> v .: "pitch_salience"
    <*> parseJSON value
    <*> parseJSON value
    <*> v .: "zerocrossingrate"


newtype LowLevelException = LowLevelException String deriving(Eq, Ord, Read, Show)
instance Exception LowLevelException