    {-# LANGUAGE CPP #-}
    {-# LANGUAGE DeriveDataTypeable #-}
    {-# LANGUAGE DeriveGeneric #-}
    {-# LANGUAGE MultiParamTypeClasses #-}
    {-# LANGUAGE RecordWildCards #-}
    {-# LANGUAGE TemplateHaskell #-}
    {-# LANGUAGE TypeFamilies #-}
    {-# LANGUAGE ViewPatterns #-}

    #include "thyme.h"

    module Data.Thyme.Calendar.WeekdayOfMonth
        ( Year, Month, DayOfWeek
        , module Data.Thyme.Calendar.WeekdayOfMonth
        ) where

    import Prelude
    import Control.Applicative
    import Control.Arrow
    import Control.DeepSeq
    import Control.Lens
    import Control.Monad
    import Data.AffineSpace
    import Data.Bits
    import Data.Data
    import Data.Thyme.Calendar
    import Data.Thyme.Calendar.Internal
    #if __GLASGOW_HASKELL__ == 704
    import qualified Data.Vector.Generic
    import qualified Data.Vector.Generic.Mutable
    #endif
    import Data.Vector.Unboxed.Deriving
    import GHC.Generics (Generic)
    import System.Random
    import Test.QuickCheck hiding ((.&.))

    data WeekdayOfMonth = WeekdayOfMonth
        { womYear :: {-# UNPACK #-}!Year
        , womMonth :: {-# UNPACK #-}!Month
        , womNth :: {-# UNPACK #-}!Int -- ^ ±1–5, negative means n-th last
        , womDayOfWeek :: {-# UNPACK #-}!DayOfWeek
        } deriving (INSTANCES_USUAL, Show)

    derivingUnbox "WeekdayOfMonth"
        [t| WeekdayOfMonth -> Int |]
        [| \ WeekdayOfMonth {..} -> shiftL womYear 11 .|. shiftL womMonth 7
            .|. shiftL (womNth + 5) 3 .|. womDayOfWeek |]
        [| \ n -> WeekdayOfMonth (shiftR n 11) (shiftR n 7 .&. 0xf)
            (shiftR n 3 - 5) (n .&. 0x7) |]

    instance NFData WeekdayOfMonth

    instance Bounded WeekdayOfMonth where
        minBound = minBound ^. weekdayOfMonth
        maxBound = maxBound ^. weekdayOfMonth

    instance Random WeekdayOfMonth where
        randomR = randomIsoR weekdayOfMonth
        random = first (^. weekdayOfMonth) . random

    instance Arbitrary WeekdayOfMonth where
        arbitrary = view weekdayOfMonth <$> arbitrary
        shrink wom = view weekdayOfMonth <$> shrink (weekdayOfMonth # wom)

    instance CoArbitrary WeekdayOfMonth where
        coarbitrary (WeekdayOfMonth y m n d)
            = coarbitrary y . coarbitrary m
            . coarbitrary n . coarbitrary d

    {-# INLINE weekdayOfMonth #-}
    weekdayOfMonth :: Iso' Day WeekdayOfMonth
    weekdayOfMonth = iso toWeekday fromWeekday where

        {-# INLINEABLE toWeekday #-}
        toWeekday :: Day -> WeekdayOfMonth
        toWeekday day@(view ordinalDate -> ord) = WeekdayOfMonth y m n wd where
            YearMonthDay y m d = ord ^. yearMonthDay
            WeekDate _ _ wd = toWeekOrdinal ord day
            n = 1 + div (d - 1) 7

        {-# INLINEABLE fromWeekday #-}
        fromWeekday :: WeekdayOfMonth -> Day
        fromWeekday (WeekdayOfMonth y m n wd) = refDay .+^ s * offset where
            refOrd = yearMonthDay # YearMonthDay y m
                (if n < 0 then monthLength (isLeapYear y) m else 1)
            refDay = ordinalDate # refOrd
            WeekDate _ _ wd1 = toWeekOrdinal refOrd refDay
            s = signum n
            wo = s * (wd - wd1)
            offset = (abs n - 1) * 7 + if wo < 0 then wo + 7 else wo

    {-# INLINEABLE weekdayOfMonthValid #-}
    weekdayOfMonthValid :: WeekdayOfMonth -> Maybe Day
    weekdayOfMonthValid (WeekdayOfMonth y m n wd) = (refDay .+^ s * offset)
            <$ guard (n /= 0 && 1 <= wd && wd <= 7 && offset < len) where
        len = monthLength (isLeapYear y) m
        refOrd = yearMonthDay # YearMonthDay y m (if n < 0 then len else 1)
        refDay = ordinalDate # refOrd
        WeekDate _ _ wd1 = toWeekOrdinal refOrd refDay
        s = signum n
        wo = s * (wd - wd1)
        offset = (abs n - 1) * 7 + if wo < 0 then wo + 7 else wo

    -- * Lenses
    LENS(WeekdayOfMonth,womYear,Year)
    LENS(WeekdayOfMonth,womMonth,Month)
    LENS(WeekdayOfMonth,womNth,Int)
    LENS(WeekdayOfMonth,womDayOfWeek,DayOfWeek)