module Data.Geometry.Angle where

import Data.Fixed (mod')

newtype Angle = Angle Double deriving (Eq, Show, Read, Ord)

ccwFrom :: Angle -> Angle -> Bool
(Angle x) `ccwFrom` (Angle y) | x > y && x - y <  pi = True
                              | x < y && y - x >= pi = True
                              | otherwise            = False

ccwEqFrom :: Angle -> Angle -> Bool
a1 `ccwEqFrom` a2 = a1 == a2 || a1 `ccwFrom` a2

cwFrom :: Angle -> Angle -> Bool
cwFrom a b = not $ ccwEqFrom a b

cwEqFrom :: Angle -> Angle -> Bool
cwEqFrom a b = not $ ccwFrom a b

instance Num Angle where
    fromInteger = mkAngle . fromInteger
    (+) = error "Angles can't be added"
    (*) = error "Angles can't be multiplied"
    abs = error "Angles don't have absolute values"
    signum = error "Angles don't have signum"
    negate = error "Angle can't be negated"

instance Fractional Angle where
    fromRational = mkAngle . fromRational
    recip = error "Angles don't have a reciprocal"

sin' :: Angle -> Double
sin' (Angle x) = sin x

cos' :: Angle -> Double
cos' (Angle x) = cos x

data AngleSpan = AngleSpan !Angle !Angle deriving (Eq, Show, Read)

mkAngle :: Double -> Angle
mkAngle ang | ang >= 0 && ang < 2 * pi = Angle ang
            | otherwise                = Angle $! ang `mod'` (2 * pi)

angleInSpan :: Angle -> AngleSpan -> Bool
angleInSpan ang (AngleSpan first second) =
    (ang `ccwEqFrom` first && second `ccwEqFrom` ang) ||
    (ang `ccwEqFrom` first && first `ccwFrom` second) ||
    (ang `cwEqFrom` second && second `cwFrom` first)

compareRelative :: Angle -> Angle -> Angle -> Ordering
compareRelative orig a b
    | a == b                              = EQ
    | a `ccwFrom` orig && b `cwFrom` orig = LT
    | b `ccwFrom` orig && a `cwFrom` orig = GT
    | a `ccwFrom` b                       = GT
    | otherwise                           = LT