{-# OPTIONS -fglasgow-exts -fallow-undecidable-instances #-}
-----------------------------------------------------------------------------
-- Module      :  Dimensional
-- Copyright   :  (c) Aaron Denney 2004
-- License     :  None, so far.  
-- 
-- Maintainer  :  wnoise@ofb.net
-- Stability   :  experimental
-- Portability :  GHC only -- multiparameter type classes, fundeps
--
-- compile time unit-analysis for Dimensional safety.
-- uses the trick oleg elegantly explains at
-- http://okmij.org/ftp/Haskell/number-parameterized-types.html
-- Each dimensioned number is a tuple of the various base units, and the
-- actual number.
-----------------------------------------------------------------------------

module Dimensional where

import BB3
import BB3Add
import BB3Neg
import TNum
import TAdd
import TNeg

-- data (Num n, TNum u1, TNum u2) => Dim n u1 u2 = D n u1 u2
data Dim n u1 u2 = D n u1 u2

class DimMult a b c | a b -> c where
      (*%) :: a -> b -> c

class DimDiv a b c | a b -> c where
      (/%) :: a -> b -> c

class DimAdd a where
      (+%) :: a -> a -> a
      (-%) :: a -> a -> a

instance Eq n => Eq (Dim n a b) where
      (D x _ _) == (D y _ _) = x == y

instance Num n => Num (Dim n Zero Zero) where
      (+) = (+%)
      (-) = (-%)
      (D x _ _) * (D y _ _) = D (x * y) Zero Zero
      fromInteger x = D (fromInteger x) Zero Zero
      signum (D x Zero Zero) = D (signum x) Zero Zero
      abs (D x Zero Zero) = D (abs x) Zero Zero

toUnit :: Num n => n -> Dim n Zero Zero
toUnit x = D x Zero Zero

instance (Num n, TNum b, TNum c) => DimAdd (Dim n b c) where
      (D n1 y1 z1) +% (D n2 y2 z2) = D (n1 + n2) y1 z1 
      (D n1 y1 z1) -% (D n2 y2 z2) = D (n1 - n2) y1 z1

instance (TNum ar, TNum br, Num n, Sum a1 a2 ar, Sum b1 b2 br) =>
    DimMult (Dim n a1 b1)  (Dim n a2 b2) (Dim n ar br) where
       -- (D x a1 b1) *% (D y a2 b2) = D (x*y) (dsum a1 a2) (dsum b1 b2)
       (D x a1 b1) *% (D y a2 b2) = D (x*y) val val

instance (TNum ar, TNum br, Fractional n, TNeg a2 ai, Sum a1 ai ar, TNeg b2 bi, Sum b1 bi br) =>
    DimDiv (Dim n a1 b1) (Dim n a2 b2) (Dim n ar br) where
       -- (D x a1 b1) /% (D y a2 b2) = D (x/y) (dsum a1 $ neg a2) (dsum b1 $ neg b2)
       (D x a1 b1) /% (D y a2 b2) = D (x/y) val val
           

-- meters :: Num a => Dim a (TDp Zero) Zero
meters  = D 1 (TDp Zero) (Zero) 
seconds = D 1 (Zero) (TDp Zero) 
speedoflight = D 299792458 (TDp Zero) (TDn Zero)

instance (Num a, TNum b, TNum c) => Show (Dim a b c) where
    show (D n a b) = show n ++ optspace ++ (unitstring "m" meterexp) ++
                               optdash  ++ (unitstring "s" secondexp) where
         meterexp = c2Int a
         secondexp = c2Int b
         optspace = if (meterexp /= 0) || (secondexp /= 0) then " " else ""
         optdash  = if (meterexp /= 0) && (secondexp /= 0) then "-" else ""
         unitstring unit 0 = ""
         unitstring unit 1 = unit
         unitstring unit n = unit ++ "^" ++ (show n)
