module QuantLib.Time.DayCounter
  ( module QuantLib.Time.DayCounter,
  )
where

import Data.Time.Calendar (Day (..), toGregorian)
import QuantLib.Time.Date (Date)

-- | Day counter type class
class DayCounter m where
  -- | Name of day counter
  dcName :: m -> String

  -- | Number of business days inbetween
  dcCount :: m -> Date -> Date -> Int

  -- | Year fraction
  dcYearFraction :: m -> Date -> Date -> Double

{-
data SimpleDayCounter = SimpleDayCounter

instance DayCounter SimpleDayCounter where
        dcName _        = "Simple"
        dcCount         = undefined
        dcYearFraction  = undefined
-}

-- | Thirty day counters as in QuantLib
data Thirty360 = ThirtyUSA | ThirtyEuropean | ThirtyItalian

instance DayCounter Thirty360 where
  dcName :: Thirty360 -> String
dcName Thirty360
ThirtyUSA = String
"Thirty USA"
  dcName Thirty360
ThirtyEuropean = String
"Thirty Euro"
  dcName Thirty360
ThirtyItalian = String
"Thirty Italian"

  dcYearFraction :: Thirty360 -> Date -> Date -> Double
dcYearFraction Thirty360
dc Date
fromDate Date
toDate = forall a b. (Integral a, Num b) => a -> b
fromIntegral (forall m. DayCounter m => m -> Date -> Date -> Int
dcCount Thirty360
dc Date
fromDate Date
toDate) forall a. Fractional a => a -> a -> a
/ Double
360.0

  dcCount :: Thirty360 -> Date -> Date -> Int
dcCount Thirty360
ThirtyUSA Date
fd Date
td = Int
360 forall a. Num a => a -> a -> a
* (Int
yy2 forall a. Num a => a -> a -> a
- Int
yy1) forall a. Num a => a -> a -> a
+ Int
30 forall a. Num a => a -> a -> a
* (Int
mm2 forall a. Num a => a -> a -> a
- Int
mm1 forall a. Num a => a -> a -> a
-Int
1) forall a. Num a => a -> a -> a
+ forall a. Ord a => a -> a -> a
max Int
0 (Int
30 forall a. Num a => a -> a -> a
- Int
dd1) forall a. Num a => a -> a -> a
+ forall a. Ord a => a -> a -> a
min Int
30 Int
dd2
    where
      (Int
yy1, Int
mm1, Int
dd1) = Date -> (Int, Int, Int)
intGregorian Date
fd
      (Int
yy2, Int
m2, Int
d2) = Date -> (Int, Int, Int)
intGregorian Date
td
      (Int
dd2, Int
mm2) = forall {a} {a} {b}.
(Ord a, Num a, Num a, Num b, Eq a) =>
a -> a -> b -> (a, b)
adjust Int
dd1 Int
d2 Int
m2
      adjust :: a -> a -> b -> (a, b)
adjust a
x1 a
x2 b
z2
        | a
x2 forall a. Eq a => a -> a -> Bool
== a
31 Bool -> Bool -> Bool
&& a
x1 forall a. Ord a => a -> a -> Bool
< a
30 = (a
1, b
z2 forall a. Num a => a -> a -> a
+ b
1)
        | Bool
otherwise = (a
x2, b
z2)
  dcCount Thirty360
ThirtyEuropean Date
fd Date
td = Int
360 forall a. Num a => a -> a -> a
* (Int
yy2 forall a. Num a => a -> a -> a
- Int
yy1) forall a. Num a => a -> a -> a
+ Int
30 forall a. Num a => a -> a -> a
* (Int
m2 forall a. Num a => a -> a -> a
- Int
m1 forall a. Num a => a -> a -> a
-Int
1) forall a. Num a => a -> a -> a
+ forall a. Ord a => a -> a -> a
max Int
0 (Int
30 forall a. Num a => a -> a -> a
- Int
d1) forall a. Num a => a -> a -> a
+ forall a. Ord a => a -> a -> a
min Int
30 Int
d2
    where
      (Int
yy1, Int
m1, Int
d1) = Date -> (Int, Int, Int)
intGregorian Date
fd
      (Int
yy2, Int
m2, Int
d2) = Date -> (Int, Int, Int)
intGregorian Date
td
  dcCount Thirty360
ThirtyItalian Date
fd Date
td = Int
360 forall a. Num a => a -> a -> a
* (Int
yy2 forall a. Num a => a -> a -> a
- Int
yy1) forall a. Num a => a -> a -> a
+ Int
30 forall a. Num a => a -> a -> a
* (Int
mm2 forall a. Num a => a -> a -> a
- Int
mm1 forall a. Num a => a -> a -> a
-Int
1) forall a. Num a => a -> a -> a
+ forall a. Ord a => a -> a -> a
max Int
0 (Int
30 forall a. Num a => a -> a -> a
- Int
dd1) forall a. Num a => a -> a -> a
+ forall a. Ord a => a -> a -> a
min Int
30 Int
dd2
    where
      (Int
yy1, Int
mm1, Int
d1) = Date -> (Int, Int, Int)
intGregorian Date
fd
      (Int
yy2, Int
mm2, Int
d2) = Date -> (Int, Int, Int)
intGregorian Date
td
      dd1 :: Int
dd1 = forall {a} {a}. (Ord a, Num a, Num a, Eq a) => a -> a -> a
adjust Int
d1 Int
mm1
      dd2 :: Int
dd2 = forall {a} {a}. (Ord a, Num a, Num a, Eq a) => a -> a -> a
adjust Int
d2 Int
mm2
      adjust :: a -> a -> a
adjust a
x1 a
z1
        | a
z1 forall a. Eq a => a -> a -> Bool
== a
2 Bool -> Bool -> Bool
&& a
x1 forall a. Ord a => a -> a -> Bool
> a
27 = a
30
        | Bool
otherwise = a
x1

intGregorian :: Day -> (Int, Int, Int)
intGregorian :: Date -> (Int, Int, Int)
intGregorian Date
date = (forall a b. (Integral a, Num b) => a -> b
fromIntegral Year
y, Int
m, Int
d)
  where
    (Year
y, Int
m, Int
d) = Date -> (Year, Int, Int)
toGregorian Date
date