{- |
  Various facilities for dealing with vectors, vector spaces and coordinate axies generically.

{-# LANGUAGE FlexibleContexts, MultiParamTypeClasses, TypeFamilies #-}

module Data.Vector.Fancy where

import Data.Vector.Class
import Data.Vector.V1
import Data.Vector.V2
import Data.Vector.V3
import Data.Vector.V4

-- * Vector spaces

{- |
  This class deals with any type that has a spatial dimensionallity.
  This includes coordinate transforms, bounding boxes, and so on.

  Null instances are provided for all the vector types. (E.g.,
  @Point Vector3 = Vector3@.)
class (Vector (Point x)) => HasSpace x where
  -- | Give the appropriate kind of vector for this type.
  type Point x :: *

instance HasSpace Vector1 where
  type Point Vector1 = Vector1

instance HasSpace Vector2 where
  type Point Vector2 = Vector2

instance HasSpace Vector3 where
  type Point Vector3 = Vector3

instance HasSpace Vector4 where
  type Point Vector4 = Vector4

-- * Vector axies

-- | The X-axis (first axis).
data AxisX = AxisX deriving Show

-- | The Y-axis (second axis).
data AxisY = AxisY deriving Show

-- | The Z-axis (third axis).
data AxisZ = AxisZ deriving Show

-- | The W-axis (fourth axis).
data AxisW = AxisW deriving Show

-- | Class for generically reading/writing vector coordinates.
class VectorAxis vector axis where
  -- | Read from the specified coordinate axis.
  get_coord :: axis -> vector -> Scalar

  -- | Replace the existing value of the given coordinate axis.
  set_coord :: axis -> Scalar -> vector -> vector

instance VectorAxis Vector1 AxisX where
  get_coord _ = v1x
  set_coord _ x _ = Vector1 x

instance VectorAxis Vector2 AxisX where
  get_coord _ = v2x
  set_coord _ x v = v {v2x = x}

instance VectorAxis Vector3 AxisX where
  get_coord _ = v3x
  set_coord _ x v = v {v3x = x}

instance VectorAxis Vector4 AxisX where
  get_coord _ = v4x
  set_coord _ x v = v {v4x = x}

instance VectorAxis Vector2 AxisY where
  get_coord _ = v2y
  set_coord _ y v = v {v2y = y}

instance VectorAxis Vector3 AxisY where
  get_coord _ = v3y
  set_coord _ y v = v {v3y = y}

instance VectorAxis Vector4 AxisY where
  get_coord _ = v4y
  set_coord _ y v = v {v4y = y}

instance VectorAxis Vector3 AxisZ where
  get_coord _ = v3z
  set_coord _ z v = v {v3z = z}

instance VectorAxis Vector4 AxisZ where
  get_coord _ = v4z
  set_coord _ z v = v {v4z = z}

instance VectorAxis Vector4 AxisW where
  get_coord _ = v4w
  set_coord _ w v = v {v4w = w}

-- * Vector projection

{- |
  This class enables you to take a vector with N dimensions and
  project it into an N+1 dimensional space (and also take the inverse
  projection to get back again).
class (Vector v, Vector (ProjectTo v)) => Project v where
  -- | The next-largest vector type. (E.g., 'ProjectTo' 'Vector2' = 'Vector3'.)
  type ProjectTo v :: *

  -- | Reduce number of dimensions by one. (Return the dropped dimension as a @Scalar@.)
  orthographic_down ::  ProjectTo v -> (v, Scalar)

  -- | Increase number of dimensions by one. (Supply value for new dimension as a @Scalar@.)
  orthographic_up   :: (v, Scalar)  -> ProjectTo v

  -- | Perspective-project to N-1 dimensions. (Also return the distance from the camera as a @Scalar@.)
  perspective_down ::  ProjectTo v  -> (v, Scalar)

  -- | Inverse-perspective project into N+1 dimension. (Supply the distance from the camera as a @Scalar@.)
  perspective_up   :: (v, Scalar)   ->  ProjectTo v

instance Project Vector1 where
  type ProjectTo Vector1 = Vector2
  orthographic_down (Vector2 x  y) = (Vector1 x, y)
  orthographic_up   (Vector1 x, y) = (Vector2 x  y)

  perspective_down  (Vector2 x  y) = (Vector1 (x/y), y)
  perspective_up    (Vector1 x, y) = (Vector2 (x*y)  y)

instance Project Vector2 where
  type ProjectTo Vector2 = Vector3
  orthographic_down (Vector3 x y  z) = (Vector2 x y, z)
  orthographic_up   (Vector2 x y, z) = (Vector3 x y  z)

  perspective_down  (Vector3 x y  z) = (Vector2 (x/z) (y/z), z)
  perspective_up    (Vector2 x y, z) = (Vector3 (x*z) (y*z)  z)

instance Project Vector3 where
  type ProjectTo Vector3 = Vector4
  orthographic_down (Vector4 x y z  w) = (Vector3 x y z, w)
  orthographic_up   (Vector3 x y z, w) = (Vector4 x y z  w)

  perspective_down  (Vector4 x y z  w) = (Vector3 (x/w) (y/w) (z/w), w)
  perspective_up    (Vector3 x y z, w) = (Vector4 (x*w) (y*w) (z*w)  w)