{-|
Module      : SimFin.Plus
Description : SimFin+ API.
Copyright   : (c) Owen Shepherd, 2022
License     : MIT
Maintainer  : owen@owen.cafe
-}

{-# LANGUAGE OverloadedStrings #-}

module SimFin.Plus
  ( SimFinContext(..)
  , Industry(..)

  , CompanyListingRow(..)
  , CompanyInfoRow(..)

  , GeneralBalanceSheetRow(..)
  , BankBalanceSheetRow(..)
  , InsuranceBalanceSheetRow(..)

  , GeneralProfitAndLossRow(..)
  , BankProfitAndLossRow(..)
  , InsuranceProfitAndLossRow(..)

  , GeneralCashFlowRow(..)
  , BankCashFlowRow(..)
  , InsuranceCashFlowRow(..)

  , DerivedRow(..)
  , PricesRow(..)
  , RatiosRow(..)

  , PricesAndRatiosRow(..)

  , PricesQuery(..)
  , StatementQuery(..)

  , StockRef(..)
  , FiscalPeriod(..)

  , ApiError(..)
  , ApiResult

  , createDefaultContext
  , fetchCompanyList

  , fetchCompanyInfo
  , fetchBalanceSheets
  , fetchProfitsAndLosses
  , fetchCashFlows
  , fetchDerived
  , fetchPrices
  , fetchPricesAndRatios
  ) where

import Control.Monad.Catch
import Control.Monad.IO.Class
import Data.Functor.Syntax
import Data.List.NonEmpty (NonEmpty)

import SimFin.Common
import SimFin.Internal
import SimFin.Types.BalanceSheet
import SimFin.Types.CompanyInfo
import SimFin.Types.CompanyListing
import SimFin.Types.CashFlow
import SimFin.Types.Derived
import SimFin.Types.FiscalPeriod
import SimFin.Types.Industry
import SimFin.Types.Prices
import SimFin.Types.PricesQuery
import SimFin.Types.PricesAndRatios
import SimFin.Types.ProfitAndLoss
import SimFin.Types.Ratios
import SimFin.Types.StatementQuery
import SimFin.Types.StockRef
import SimFin.Util

------
-- General Company Info
------

-- | Fetch general company information.
-- See the [SimFin docs](https://simfin.com/api/v2/documentation/#tag/Company/paths/~1companies~1general/get).

fetchCompanyInfo
  :: (MonadThrow m, MonadIO m)
  => SimFinContext
  -> NonEmpty StockRef
  -> m (ApiResult [CompanyInfoRow])
fetchCompanyInfo :: SimFinContext
-> NonEmpty StockRef -> m (ApiResult [CompanyInfoRow])
fetchCompanyInfo SimFinContext
ctx NonEmpty StockRef
refs =
  SimFinContext
-> ByteString -> [QueryParam] -> m (ApiResult [CompanyInfoRow])
forall (m :: * -> *) a.
(MonadIO m, FromJSON a) =>
SimFinContext -> ByteString -> [QueryParam] -> m (ApiResult a)
performRequest SimFinContext
ctx ByteString
"companies/general" (NonEmpty StockRef -> [QueryParam]
stockRefsToQueryParams NonEmpty StockRef
refs)

------
-- Balance Sheets
------

-- | Fetch a company's balance sheet statements.
-- See the [SimFin docs](https://simfin.com/api/v2/documentation/#tag/Company/paths/~1companies~1statements/get).

fetchBalanceSheets
  :: (MonadThrow m, MonadIO m)
  => SimFinContext
  -> StatementQuery
  -> m (ApiResult [IndustryBalanceSheet])
fetchBalanceSheets :: SimFinContext
-> StatementQuery -> m (ApiResult [IndustryBalanceSheet])
fetchBalanceSheets SimFinContext
ctx StatementQuery
query =
  [Industry
   [GeneralBalanceSheetRow]
   [BankBalanceSheetRow]
   [InsuranceBalanceSheetRow]]
-> [IndustryBalanceSheet]
forall a b c. [Industry [a] [b] [c]] -> [Industry a b c]
invertIndustries ([Industry
    [GeneralBalanceSheetRow]
    [BankBalanceSheetRow]
    [InsuranceBalanceSheetRow]]
 -> [IndustryBalanceSheet])
-> m (Either
        ApiError
        [Industry
           [GeneralBalanceSheetRow]
           [BankBalanceSheetRow]
           [InsuranceBalanceSheetRow]])
-> m (ApiResult [IndustryBalanceSheet])
forall (f0 :: * -> *) (f1 :: * -> *) a b.
(Functor f0, Functor f1) =>
(a -> b) -> f1 (f0 a) -> f1 (f0 b)
<$$> SimFinContext
-> ByteString
-> [QueryParam]
-> m (Either
        ApiError
        [Industry
           [GeneralBalanceSheetRow]
           [BankBalanceSheetRow]
           [InsuranceBalanceSheetRow]])
forall (m :: * -> *) a.
(MonadIO m, FromJSON a) =>
SimFinContext -> ByteString -> [QueryParam] -> m (ApiResult a)
performRequest SimFinContext
ctx ByteString
"companies/statements"
    ((ByteString
"statement", ByteString -> Maybe ByteString
forall a. a -> Maybe a
Just ByteString
"bs") QueryParam -> [QueryParam] -> [QueryParam]
forall a. a -> [a] -> [a]
: StatementQuery -> [QueryParam]
statementQueryToQueryParams StatementQuery
query)

------
-- P&L
------

-- | Fetch a company's profit and loss statements.
-- See the [SimFin docs](https://simfin.com/api/v2/documentation/#tag/Company/paths/~1companies~1statements/get).

fetchProfitsAndLosses
  :: (MonadThrow m, MonadIO m)
  => SimFinContext
  -> StatementQuery
  -> m (ApiResult [IndustryProfitAndLoss])
fetchProfitsAndLosses :: SimFinContext
-> StatementQuery -> m (ApiResult [IndustryProfitAndLoss])
fetchProfitsAndLosses SimFinContext
ctx StatementQuery
query =
  [Industry
   [GeneralProfitAndLossRow]
   [BankProfitAndLossRow]
   [InsuranceProfitAndLossRow]]
-> [IndustryProfitAndLoss]
forall a b c. [Industry [a] [b] [c]] -> [Industry a b c]
invertIndustries ([Industry
    [GeneralProfitAndLossRow]
    [BankProfitAndLossRow]
    [InsuranceProfitAndLossRow]]
 -> [IndustryProfitAndLoss])
-> m (Either
        ApiError
        [Industry
           [GeneralProfitAndLossRow]
           [BankProfitAndLossRow]
           [InsuranceProfitAndLossRow]])
-> m (ApiResult [IndustryProfitAndLoss])
forall (f0 :: * -> *) (f1 :: * -> *) a b.
(Functor f0, Functor f1) =>
(a -> b) -> f1 (f0 a) -> f1 (f0 b)
<$$> SimFinContext
-> ByteString
-> [QueryParam]
-> m (Either
        ApiError
        [Industry
           [GeneralProfitAndLossRow]
           [BankProfitAndLossRow]
           [InsuranceProfitAndLossRow]])
forall (m :: * -> *) a.
(MonadIO m, FromJSON a) =>
SimFinContext -> ByteString -> [QueryParam] -> m (ApiResult a)
performRequest SimFinContext
ctx ByteString
"companies/statements"
    ((ByteString
"statement", ByteString -> Maybe ByteString
forall a. a -> Maybe a
Just ByteString
"pl") QueryParam -> [QueryParam] -> [QueryParam]
forall a. a -> [a] -> [a]
: StatementQuery -> [QueryParam]
statementQueryToQueryParams StatementQuery
query)

-----
-- Cash Flows
------

-- | Fetch a company's cash flow statements.
-- See the [SimFin docs](https://simfin.com/api/v2/documentation/#tag/Company/paths/~1companies~1statements/get).

fetchCashFlows
  :: (MonadThrow m, MonadIO m)
  => SimFinContext
  -> StatementQuery
  -> m (ApiResult [IndustryCashFlow])
fetchCashFlows :: SimFinContext -> StatementQuery -> m (ApiResult [IndustryCashFlow])
fetchCashFlows SimFinContext
ctx StatementQuery
query =
  [Industry
   [GeneralCashFlowRow] [BankCashFlowRow] [InsuranceCashFlowRow]]
-> [IndustryCashFlow]
forall a b c. [Industry [a] [b] [c]] -> [Industry a b c]
invertIndustries ([Industry
    [GeneralCashFlowRow] [BankCashFlowRow] [InsuranceCashFlowRow]]
 -> [IndustryCashFlow])
-> m (Either
        ApiError
        [Industry
           [GeneralCashFlowRow] [BankCashFlowRow] [InsuranceCashFlowRow]])
-> m (ApiResult [IndustryCashFlow])
forall (f0 :: * -> *) (f1 :: * -> *) a b.
(Functor f0, Functor f1) =>
(a -> b) -> f1 (f0 a) -> f1 (f0 b)
<$$> SimFinContext
-> ByteString
-> [QueryParam]
-> m (Either
        ApiError
        [Industry
           [GeneralCashFlowRow] [BankCashFlowRow] [InsuranceCashFlowRow]])
forall (m :: * -> *) a.
(MonadIO m, FromJSON a) =>
SimFinContext -> ByteString -> [QueryParam] -> m (ApiResult a)
performRequest SimFinContext
ctx ByteString
"companies/statements"
    ((ByteString
"statement", ByteString -> Maybe ByteString
forall a. a -> Maybe a
Just ByteString
"cf") QueryParam -> [QueryParam] -> [QueryParam]
forall a. a -> [a] -> [a]
: StatementQuery -> [QueryParam]
statementQueryToQueryParams StatementQuery
query)

------
-- Derived
------

-- | Fetch a company's derived figures.
-- See the [SimFin docs](https://simfin.com/api/v2/documentation/#tag/Company/paths/~1companies~1statements/get).

fetchDerived
  :: (Read a, RealFrac a, MonadThrow m, MonadIO m)
  => SimFinContext
  -> StatementQuery
  -> m (ApiResult [DerivedRow a])
fetchDerived :: SimFinContext -> StatementQuery -> m (ApiResult [DerivedRow a])
fetchDerived SimFinContext
ctx StatementQuery
query =
  [[DerivedRow a]] -> [DerivedRow a]
forall a. Monoid a => [a] -> a
mconcat ([[DerivedRow a]] -> [DerivedRow a])
-> m (Either ApiError [[DerivedRow a]])
-> m (ApiResult [DerivedRow a])
forall (f0 :: * -> *) (f1 :: * -> *) a b.
(Functor f0, Functor f1) =>
(a -> b) -> f1 (f0 a) -> f1 (f0 b)
<$$> SimFinContext
-> ByteString
-> [QueryParam]
-> m (Either ApiError [[DerivedRow a]])
forall (m :: * -> *) a.
(MonadIO m, FromJSON a) =>
SimFinContext -> ByteString -> [QueryParam] -> m (ApiResult a)
performRequest SimFinContext
ctx ByteString
"companies/statements"
    ((ByteString
"statement", ByteString -> Maybe ByteString
forall a. a -> Maybe a
Just ByteString
"derived") QueryParam -> [QueryParam] -> [QueryParam]
forall a. a -> [a] -> [a]
: StatementQuery -> [QueryParam]
statementQueryToQueryParams StatementQuery
query)

------
-- Prices
------

-- | Fetch a company's historical share prices.
-- See the [SimFin docs](https://simfin.com/api/v2/documentation/#tag/Company/paths/~1companies~1prices/get).

fetchPrices
  :: (Read a, RealFrac a, MonadThrow m, MonadIO m)
  => SimFinContext
  -> PricesQuery
  -> m (ApiResult [PricesRow a])
fetchPrices :: SimFinContext -> PricesQuery -> m (ApiResult [PricesRow a])
fetchPrices SimFinContext
ctx PricesQuery
query =
  [[PricesRow a]] -> [PricesRow a]
forall a. Monoid a => [a] -> a
mconcat ([[PricesRow a]] -> [PricesRow a])
-> ([PricesKeyed a] -> [[PricesRow a]])
-> [PricesKeyed a]
-> [PricesRow a]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (PricesKeyed a -> [PricesRow a])
-> [PricesKeyed a] -> [[PricesRow a]]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap PricesKeyed a -> [PricesRow a]
forall a. PricesKeyed a -> [PricesRow a]
unKeyPrices ([PricesKeyed a] -> [PricesRow a])
-> m (Either ApiError [PricesKeyed a])
-> m (ApiResult [PricesRow a])
forall (f0 :: * -> *) (f1 :: * -> *) a b.
(Functor f0, Functor f1) =>
(a -> b) -> f1 (f0 a) -> f1 (f0 b)
<$$> SimFinContext
-> ByteString
-> [QueryParam]
-> m (Either ApiError [PricesKeyed a])
forall (m :: * -> *) a.
(MonadIO m, FromJSON a) =>
SimFinContext -> ByteString -> [QueryParam] -> m (ApiResult a)
performRequest SimFinContext
ctx ByteString
"companies/prices"
    (PricesQuery -> [QueryParam]
pricesQueryToQueryParams PricesQuery
query)

-- | Fetch a company's historical share prices, along with key ratios.
-- See the [SimFin docs](https://simfin.com/api/v2/documentation/#tag/Company/paths/~1companies~1prices/get).

fetchPricesAndRatios
  :: (Read a, RealFrac a, MonadThrow m, MonadIO m)
  => SimFinContext
  -> PricesQuery
  -> m (ApiResult [PricesAndRatiosRow a])
fetchPricesAndRatios :: SimFinContext
-> PricesQuery -> m (ApiResult [PricesAndRatiosRow a])
fetchPricesAndRatios SimFinContext
ctx PricesQuery
query =
  [[PricesAndRatiosRow a]] -> [PricesAndRatiosRow a]
forall a. Monoid a => [a] -> a
mconcat ([[PricesAndRatiosRow a]] -> [PricesAndRatiosRow a])
-> ([PricesAndRatiosKeyed a] -> [[PricesAndRatiosRow a]])
-> [PricesAndRatiosKeyed a]
-> [PricesAndRatiosRow a]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (PricesAndRatiosKeyed a -> [PricesAndRatiosRow a])
-> [PricesAndRatiosKeyed a] -> [[PricesAndRatiosRow a]]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap PricesAndRatiosKeyed a -> [PricesAndRatiosRow a]
forall a. PricesAndRatiosKeyed a -> [PricesAndRatiosRow a]
unKeyPricesAndRatios ([PricesAndRatiosKeyed a] -> [PricesAndRatiosRow a])
-> m (Either ApiError [PricesAndRatiosKeyed a])
-> m (ApiResult [PricesAndRatiosRow a])
forall (f0 :: * -> *) (f1 :: * -> *) a b.
(Functor f0, Functor f1) =>
(a -> b) -> f1 (f0 a) -> f1 (f0 b)
<$$> SimFinContext
-> ByteString
-> [QueryParam]
-> m (Either ApiError [PricesAndRatiosKeyed a])
forall (m :: * -> *) a.
(MonadIO m, FromJSON a) =>
SimFinContext -> ByteString -> [QueryParam] -> m (ApiResult a)
performRequest SimFinContext
ctx ByteString
"companies/prices"
    ((ByteString
"ratios", Maybe ByteString
forall a. Maybe a
Nothing) QueryParam -> [QueryParam] -> [QueryParam]
forall a. a -> [a] -> [a]
: PricesQuery -> [QueryParam]
pricesQueryToQueryParams PricesQuery
query)