{-# LANGUAGE FlexibleInstances #-}
-- | This module describes the internal structure of the `Point` type
-- class and the `Point'` instance of said type class.

module Geom2d.Point.Internal
    ( Point' (..)
    , Point (..)
    , magnitude
    )

where

import Data.AEq
import Geom2d.Rotation
import qualified Prelude ((^))
import Prelude hiding ((^))
import Test.QuickCheck

(^) :: Num a => a -> Int -> a
(^) = (Prelude.^)

-- | This data type modells the the characteristics of vectors in 2
-- dimensional space.  You should construct it via `fromCoords`.
newtype Point' a = Point' (a,a) deriving (Show, Read, Eq)

class Point p where
  x :: p a -> a
  y :: p a -> a
  fromCoords :: a -> a -> p a

-- | Return the magnitude of a vector.
magnitude :: (Point p, Floating a, Num a) => p a -> a
magnitude p =
  sqrt (x p ^ (2::Int) + y p ^ (2::Int))

instance Point Point' where
  x (Point' p) = fst p
  y (Point' p) = snd p
  fromCoords a b = Point' (a, b)

-- | Implementing the `Num` type class allows us to add and subtract
-- vectors.  Multiplication is implemented in terms of complex number
-- multiplication.  If you want to multiply using the dot or cross
-- products use the appropriate functions from this package.
instance (Eq a, Num a, Fractional a, Floating a, Point p) => Num (p a) where
  p + q = fromCoords (x p + x q) (y p + y q)
  p - q = fromCoords (x p - x q) (y p - y q)
  a * b = fromCoords (x a*x b - y a*y b) (x a*y b + y a*x b)
  abs p = fromCoords (sqrt (x p^2 + y p^2)) 0
  signum p
      | x p == fromIntegral (0::Int) && y p == fromIntegral (0::Int) =
          fromCoords 0 0
      | otherwise = fromCoords (x p*l) (y p*l)
      where l = 1 / x (abs p)
  fromInteger n = fromCoords (fromInteger n) 0
  negate p = fromCoords (negate $ x p) (negate $ y p)

instance (Arbitrary a) => Arbitrary (Point' a) where
  arbitrary = curry Point' <$> arbitrary <*> arbitrary

instance (Num a, AEq a, RealFloat a) => AEq (Point' a) where
  (Point' (m,n)) ~== (Point' (p,q)) =
    m^2 + n^2 ~== p^2 + q^2 &&
    atan2 n m ~== atan2 q p

instance Functor Point' where
  fmap f (Point' (a,b)) = Point' (f a, f b)

instance Rotation Point' where
  angle p | magnitude p == 0 = Nothing
          | otherwise = Just $ atan2 (y p) (x p)
  rotate r (Point' (a,b)) = Point' (a * cos r - b * sin r, a * sin r + b * cos r)