{-# LANGUAGE FlexibleInstances, FunctionalDependencies, MultiParamTypeClasses, OverloadedStrings, TemplateHaskell #-}

{-|
   Object declarations and lenses. Should not be imported by user code.
   Please view the official documentation.

   Note that the distinction between full and simple objects is implemented as an optional Maybe field with details.
-}

module Utils.Spoty.Types where

import           Control.Applicative ((<$>), (<*>))
import           Control.Lens (makeFields)
import           Control.Monad (MonadPlus(..), mzero)
import           Data.Aeson
import           Data.Aeson.Types (Parser)
import qualified Data.HashMap.Strict as HM
import qualified Data.Text as T

type URL = T.Text

type SpotID = T.Text

type SpotURI = T.Text

-- | Require that a field is present before parsing the corresponding value.
require :: FromJSON a => T.Text -> HM.HashMap T.Text Value -> Parser (Maybe a)
require str obj =
  if HM.member str obj
    then fmap Just (parseJSON$ Object obj)
    else return Nothing

-- | Parse a map of key-value entries, wrapped in the given constructor.
parseStrMap :: MonadPlus m => HM.HashMap k Value -> (k -> T.Text -> a) -> m [a]
parseStrMap vals constr = sequence . flip map (HM.toList vals) $ \e ->
  case e of 
    (key, String val) -> return $constr key val
    _                 -> mzero

data ExternalID
  = ExternalID
  {
    _idType :: T.Text,
    _idIdentifier :: T.Text
  }
  deriving (Eq, Ord, Show)

makeFields ''ExternalID

instance FromJSON [ExternalID] where
  parseJSON (Object v) = parseStrMap v ExternalID
  parseJSON _          = mzero

data ExternalURL
  = ExternalURL
  {
    _urlType :: T.Text,
    _url :: URL
  }
  deriving (Eq, Ord, Show)

makeFields ''ExternalURL

instance FromJSON [ExternalURL] where
  parseJSON (Object v) = parseStrMap v ExternalURL
  parseJSON _          = mzero

data Image
  = Image
  {
    _imageHeight :: Maybe Int,
    _imagePath :: URL,
    _imageWidth :: Maybe Int
  }
  deriving (Eq, Ord, Show)

makeFields ''Image

instance FromJSON Image where
  parseJSON (Object v) = Image <$>
                         v .:? "height" <*>
                         v .: "url" <*>
                         v .:? "width"

  parseJSON _          = mzero

data Paging a
  = Paging
  {
    _pagingHref :: T.Text,
    _pagingItems :: [a],
    _pagingLimit :: Int,
    _pagingNext :: Maybe URL,
    _pagingOffset :: Int,
    _pagingPrevious :: Maybe URL,
    _pagingTotal :: Int
  }
  deriving (Eq, Ord, Show)

makeFields ''Paging

instance FromJSON a => FromJSON (Paging a) where
  parseJSON (Object v) = Paging <$>
                         v .: "href" <*>
                         v .: "items" <*>
                         v .: "limit" <*>
                         v .:? "next" <*>
                         v .: "offset" <*>
                         v .:? "previous" <*>
                         v .: "total"

  parseJSON _          = mzero

data User
  = User
  {
    _userExternalUrls :: [ExternalURL],
    _userHref :: URL,
    _userSpotifyID :: SpotID,
    _userSpotifyURI :: SpotURI
  }
  deriving (Eq, Ord, Show)

makeFields ''User

instance FromJSON User where
  parseJSON (Object v) = User <$>
                         v .: "external_urls" <*>
                         v .: "href" <*>
                         v .: "id" <*>
                         v .: "uri"

  parseJSON _          = mzero

data ArtistDetails
  = ArtistDetails
  {
    _artistGenres :: [T.Text],
    _artistImages :: [Image],
    _artistPopularity :: Int
  }
  deriving (Eq, Ord, Show)

makeFields ''ArtistDetails

instance FromJSON ArtistDetails where
  parseJSON (Object v) = ArtistDetails <$>
                         v .: "genres" <*>
                         v .: "images" <*>
                         v .: "popularity"

  parseJSON _          = mzero

data Artist
  = Artist
  {
    _artistExternalUrls :: [ExternalURL],
    _artistHref :: URL,
    _artistSpotifyID :: SpotID,
    _artistName :: T.Text,
    _artistSpotifyURI :: SpotURI,
    _artistDetails :: Maybe ArtistDetails
  }
  deriving (Eq, Ord, Show)

makeFields ''Artist

instance FromJSON Artist where
  parseJSON (Object v) = Artist <$>
                         v .: "external_urls" <*>
                         v .: "href" <*>
                         v .: "id" <*>
                         v .: "name" <*>
                         v .: "uri" <*> 
                         require "genres" v

  parseJSON _          = mzero

data TrackDetails
  = TrackDetails
  {
    _trackAvailableMarkets :: [T.Text],
    _trackExternalIDs :: [ExternalID],
    _trackPopularity :: Int
  }
  deriving (Eq, Ord, Show)

makeFields ''TrackDetails

instance FromJSON TrackDetails where
  parseJSON (Object v) = TrackDetails <$>
                         v .: "available_markets" <*>
                         v .: "external_ids" <*>
                         v .: "popularity"

  parseJSON _          = mzero

data Track
  = Track
  {
    _trackArtists :: [Artist],
    _trackDiscNumber :: Int,
    _trackDurationMs :: Int,
    _trackExplicit :: Bool,
    _trackExternalUrls :: [ExternalURL],
    _trackHref :: URL,
    _trackSpotifyID :: SpotID,
    _trackName :: T.Text,
    _trackPreviewURL :: URL,
    _trackNumber :: Int,
    _trackSpotifyURI :: SpotURI,
    _trackDetails :: Maybe TrackDetails
  }
  deriving (Eq, Ord, Show)

makeFields ''Track

instance FromJSON Track where
  parseJSON (Object v) = Track <$>
                         v .: "artists" <*>
                         v .: "disc_number" <*>
                         v .: "duration_ms" <*>
                         v .: "explicit" <*>
                         v .: "external_urls" <*>
                         v .: "href" <*>
                         v .: "id" <*>
                         v .: "name" <*>
                         v .: "preview_url" <*>
                         v .: "track_number" <*>
                         v .: "uri" <*> 
                         require "available_markets" v

  parseJSON _          = mzero

data AlbumDetails
  = AlbumDetails
  {
    _albumArtists :: [Artist],
    _albumExternalIDs :: [ExternalID],
    _albumGenres :: [T.Text],
    _albumPopularity :: Int,
    _albumReleaseDate :: T.Text,
    _albumReleaseDatePrecision :: T.Text,
    _albumTracks :: Paging Track
  }
  deriving (Eq, Ord, Show)

makeFields ''AlbumDetails

instance FromJSON AlbumDetails where
  parseJSON (Object v) = AlbumDetails <$>
                         v .: "artists" <*>
                         v .: "external_ids" <*>
                         v .: "genres" <*>
                         v .: "popularity" <*>
                         v .: "release_date" <*>
                         v .: "release_date_precision" <*>
                         v .: "tracks"

  parseJSON _          = mzero

data Album
  = Album
  {
    _albumType :: T.Text,
    _albumAvailableMarkets :: [T.Text],
    _albumExternalURLs :: [ExternalURL],
    _albumHref :: T.Text,
    _albumSpotifyID :: SpotID,
    _albumImages :: [Image],
    _albumName :: T.Text,
    _albumSpotifyURI :: SpotURI,
    _albumDetails :: Maybe AlbumDetails
  }
  deriving (Eq, Ord, Show)

makeFields ''Album

instance FromJSON Album where
  parseJSON (Object v) = Album <$>
                         v .: "album_type" <*>
                         v .: "available_markets" <*>
                         v .: "external_urls" <*>
                         v .: "href" <*>
                         v .: "id" <*>
                         v .: "images" <*>
                         v .: "name" <*>
                         v .: "uri" <*>
                         require "artists" v

  parseJSON _          = mzero