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

import QuantLib.Time.Date
import Data.Time.Calendar

-- | 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 ThirtyUSA        = "Thirty USA"
        dcName ThirtyEuropean   = "Thirty Euro"
        dcName ThirtyItalian    = "Thirty Italian"

        dcYearFraction  dc fromDate toDate = (fromIntegral $ dcCount dc fromDate toDate)/360.0

        dcCount ThirtyUSA fd td = 360*(yy2-yy1) + 30*(mm2-mm1-1) + (max 0 (30-dd1)) + (min 30 dd2)
                where   (y1, mm1, dd1) = toGregorian fd
                        (y2, m2, d2)   = toGregorian td
                        yy1            = fromIntegral y1
                        yy2            = fromIntegral y2
                        (dd2, mm2)     = adjust dd1 d2 m2
                        adjust x1 x2 z2
                                | x2 == 31 && x1 < 30   = (1, z2+1)
                                | otherwise             = (x2, z2)


        dcCount ThirtyEuropean fd td = 360*(yy2-yy1) + 30*(m2-m1-1) + (max 0 (30-d1)) + (min 30 d2)
                where   (y1, m1, d1)    = toGregorian fd
                        (y2, m2, d2)    = toGregorian td
                        yy1             = fromIntegral y1
                        yy2             = fromIntegral y2

        dcCount ThirtyItalian fd td = 360*(yy2-yy1) + 30*(mm2-mm1-1) + (max 0 (30-dd1)) + (min 30 dd2)
                where   (y1, mm1, d1)   = toGregorian fd
                        (y2, mm2, d2)   = toGregorian td
                        yy1             = fromIntegral y1
                        yy2             = fromIntegral y2
                        dd1             = adjust d1 mm1
                        dd2             = adjust d2 mm2
                        adjust x1 z1
                                | z1 == 2 && x1 > 27    = 30
                                | otherwise             = x1