{-# LANGUAGE TypeSynonymInstances #-}
-- |
-- Module: Graphics.Chalkboard.Types
-- Copyright: (c) 2009 Andy Gill
-- License: BSD3
--
-- Maintainer: Andy Gill <andygill@ku.edu>
-- Stability: unstable
-- Portability: ghc
--
-- This module contains the types used by chalkboard, except Board itself.
--

module Graphics.Chalkboard.Types 
	( -- * Basic types
	  UI, R, Point, Radian,
	  -- * Overlaying
	  Over(..),
	  stack,
	  -- * Scaling
	  Scale(..),
	  -- * Linear Interpolation
	  Lerp(..),
	  -- * Averaging
	  Average(..),
	  -- * Alpha Channel support
	  Alpha(..),
	 alpha, transparent, withAlpha, unAlpha,
	  -- * Z buffer support
	  Z(..),
	  -- * Constants
	  nearZero
	) where

-- | A real number.
type R = Float

-- | Unit Interval: value between 0 and 1, inclusive.
type UI = R

-- | A point in R2.
type Point = (R,R)

-- | Angle units
type Radian = Float	

-- | Close to zero; needed for @Over (Alpha c)@ instance.
nearZero :: R
nearZero = 0.0000001 

------------------------------------------------------------------------------

-- | For placing a value literally /over/ another value. The 2nd value /might/ shine through.
-- The operation /must/ be assocative.
class Over c where
  over :: c -> c -> c

instance Over Bool where
  over = (||)

instance Over (Maybe a) where
  (Just a) `over` _  = Just a
  Nothing `over` other = other

-- | 'stack' stacks a list of things over each other, where earlier elements are 'over' later elements.
-- Requires non empty lists, which can be satisfied by using an explicity
-- transparent @Board@ as one of the elements.

stack :: (Over c) => [c] -> c
stack = foldr1 over

------------------------------------------------------------------------------

-- | 'Scale' something by a value. scaling value can be bigger than 1.

class Scale c where
  scale :: R -> c -> c

instance Scale R where
  scale u v = u * v

------------------------------------------------------------------------------

-- | Linear interpolation between two values.
class Lerp a where
  lerp :: a -> a -> UI -> a

instance Lerp R where
  lerp v v' s = v + (s * (v' - v))

-- | 'Lerp' over pairs

instance (Lerp a,Lerp b) => Lerp (a,b) where
  lerp (a,b) (a',b') s = (lerp a a' s,lerp b b' s)

instance (Lerp a) => Lerp (Maybe a) where
  lerp Nothing  Nothing  _s  = Nothing
  lerp (Just a) Nothing  _s = Just a 
  lerp Nothing  (Just b) _s = Just b
  lerp (Just a) (Just b) s = Just (lerp a b s)

------------------------------------------------------------------------------

-- | 'Average' a set of values. weighting can be achived using multiple entries.

class Average a where
  -- | average is not defined for empty list
  average :: [a] -> a 

instance Average R where
  average xs = sum xs / fromIntegral (length xs)

------------------------------------------------------------------------------
-- | Channels with alpha component, the channel @is@ pre-scaled.

data Alpha c = Alpha c !UI deriving Show

-- | 'alpha' builds something that has an alpha channel, and is completely opaque.
alpha :: c -> Alpha c
alpha c = Alpha c 1.0

-- | 'transparent' builds something that has an alpha channel, and is completely transparent.
transparent :: c -> Alpha c
transparent c = Alpha c 0.0

-- | 'withAlpha' builds somethings that has a specific alpha value.
withAlpha :: (Scale c) => UI -> c -> Alpha c
withAlpha a c = Alpha (scale a c) a

-- | 'unAlpha' removes the alpha component, and returns the channel inside.
unAlpha :: (Scale c) => Alpha c -> c
unAlpha (Alpha c _a) = c -- the channel is prescaled, hence we ignore the alpha value here.

instance (Scale c,Lerp c) => Over (Alpha c) where
   -- An associative algorithm for handling the alpha channel
  over (Alpha c a) (Alpha c' a') 
	| a <= nearZero = Alpha c' a_new
	| otherwise     = Alpha (lerp c' (scale (1/a) c) a) a_new
     where
	-- can a_new be 0? only if a == 0 and a' == 0
	a_new     = a + a' * (1 - a)

------------------------------------------------------------------------------

-- | A Z buffer style Z value for a point, where lower numbers are nearer the viewer.
-- Assumes no transparency.

data Z c = Z c R deriving Show

instance Over (Z c) where
  over (Z c1 z1) (Z c2 z2) 
	| z1 <= z2  = Z c1 z1
	| otherwise = Z c2 z2