-----------------------------------------------------------------------------
-- |
-- Module      :  HCube.Utility
-- Copyright   :  (c) Todd Wegner 2012
-- License     :  BSD-style (see the LICENSE file)
-- 
-- Maintainer  :  echbar137@yahoo.co.in
-- Stability   :  provisional
-- Portability :  portable
--
-- Common utility functions, simple linear algebra. 
-----------------------------------------------------------------------------
{-# LANGUAGE Safe #-}

module HCube.Utility where

import Control.Monad (foldM, (>=>), liftM, liftM2)
import Data.List
import HCube.Data
import HCube.Common(padL)

-- | Returns square root of argument if argument is a perfect square. 
squareRoot		:: Int -> Maybe Int
squareRoot nn		= f 1 nn where
    f lo hi | lo > hi	= Nothing
	    | nn == h	= Just g
	    | nn < h	= f lo (g - 1)
	    | otherwise = f (g + 1) h where
	g		= (hi + lo) `div` 2
	h		= g * g

-- | Multiple a matrix on the left side of a vector.
(|*|)		:: Matrix -> Vec -> Vec
(|*|)(Matrix (a,d,g) (b,e,h) (c,f,i)) (x,y,z) = (a*x + b*y + c*z,
						 d*x + e*y + f*z,
						 g*x + h*y + i*z)

-- | Multiple two matrices.
(|**|)		:: Matrix -> Matrix -> Matrix
(|**|)(Matrix (a,d,g) (b,e,h) (c,f,i))
      (Matrix (j,m,p) (k,n,q) (l,o,r)) = Matrix (a*j + b*m + c*p, d*j + e*m + f*p, g*j + h*m + i*p)
						(a*k + b*n + c*q, d*k + e*n + f*q, g*k + h*n + i*q)
						(a*l + b*l + c*r, d*l + e*o + f*r, g*l + h*o + i*r )

-- | Multiple a matrix by a scalar.
multMatrix	:: Numb -> Matrix -> Matrix
multMatrix nm (Matrix (a,d,g) (b,e,h) (c,f,i)) = Matrix (nm*a,nm*d,nm*g)
							(nm*b,nm*e,h)
							(nm*c,nm*f,nm*i)
-- | The cofactor of a matrix.
cofactors	:: Matrix -> Matrix
cofactors (Matrix (a,d,g) (b,e,h) (c,f,i))
					= Matrix (e*i-f*h, c*h-b*i, b*f-c*e)
						 (f*g-d*i, a*i-c*g, c*d-a*f)
						 (d*h-e*g, b*g-a*h, a*e-b*d) 
-- | Transpose of a matrix.
transposeM	:: Matrix -> Matrix
transposeM (Matrix (a,d,g) (b,e,h) (c,f,i))  = Matrix (a,b,c) (d,e,f) (g,h,i)

-- | Inverse of a matrix.
inverse		:: Matrix -> Matrix
inverse mx	= multMatrix (det mx) . transposeM $ cofactors mx 

-- | Display a matrix.
showM			:: Matrix -> String
showM (Matrix (a,d,g) (b,e,h) (c,f,i)) = concat y where
    y		= [z a, z d, z g, "   ", z b, z e, z h, "   ", z c, z f, z i]
    z 		= padL 3 . show

doM			:: Monad m => (a -> Bool) -> (a -> m a) -> a -> m a
doM te lp		= lp >=> \s-> if te s then doM te lp s else return s

-- Inject operator
infixl 1 ~>
(~>)			:: Monad m => m a -> (a -> b -> m b) -> b -> m b
(~>) op fu st		= op >>= \s -> fu s st

(~|)			:: Monad m => (a -> m b) -> a -> m a
(~|) fu a		= fu a >> return a

infixl 4 <*
(<*)			:: Monad m => m a -> m b -> m a
(<*)			= liftM2 const

concatMapM		:: Monad m => (a -> m [b]) -> [a] -> m [b]
concatMapM bbf		= liftM concat . mapM bbf

listToMaybe           :: [a] -> Maybe a
listToMaybe []        =  Nothing
listToMaybe (a:_)     =  Just a

-- | A safe form of read.
maybeRead :: String -> Maybe Int
maybeRead = fmap fst . listToMaybe . reads

-- | Applies a function on the domain of Side x Side.
spanFaces 		:: (Enum a, Num a, Ord b) =>
			   (Side -> Side -> b) -> [(a, b)]
spanFaces fn		= zip [1..] $ sort $ f where
	f		= [fn a b | a <- sides, b <- sides, a /=b]
-- | List of the sides.
sides			= [UpS,DownS,FrontS,BackS,LeftS,RightS]

-- | List of the vectors.
vecs			= [(1,0,0),(0,1,0),(0,0,1),(-1,0,0),(0,-1,0),(0,0,-1)] :: [Vec]

-- | Multiple a vector by a scalar.
multVec			:: Numb -> Vec -> Vec
multVec nm (v1,v2,v3)	= (nm*v1, nm*v2, nm*v3)

-- | Convert a function with two vector arguments to one accepting a list of vectors.
mapVec			:: (Vec -> Vec -> a) -> [Vec] -> a
mapVec fu (v1:v2:[]) 	= fu v1 v2

-- | The determinate of a matrix.
det			:: Matrix -> Int
det (Matrix (a,d,g) (b,e,h) (c,f,i)) = a*e*i + b*f*g + c*d*h - c*e*g - b*d*i - a*f*h

-- | Calculate the determinate of a matrix constructed by three column vectors.
vecDet			:: Vec -> Vec -> Vec -> Int
vecDet v1 v2 v3		= det $ Matrix v1 v2 v3

-- | Multiple a matrix by a scalar.
matrixMult		:: Matrix -> Int -> Matrix
matrixMult (Matrix v1 v2 v3) ct = Matrix (f v1) (f v2) (f v3) where
    f			= multVec ct

-- |The cross product of two vectors.
cross			:: Vec -> Vec -> Vec
cross (a,b,c) (x,y,z)	= (b*z-y*c,a*z-c*x,a*y-b*x)

-- |Vector multipled by scalar -1.
minus			:: Vec -> Vec
minus (a,b,c)		= (-a,-b,-c)

-- |Vectors we are interested in only have one non zero component. 
vcomp			:: Vec -> Int
vcomp (vl,0,0)		= vl
vcomp (0,vl,0)		= vl
vcomp (0,0,vl)		= vl
vcomp bd		= error . show $ bd

-- |Position of non-zero vector component.
vpos			:: Vec -> Int
vpos (_,0,0)		= 1
vpos (0,_,0)		= 2
vpos (0,0,_)		= 3
vpos bd			= error . show $ bd

-- | The dot product of two vectors.
dot			:: Vec -> Vec -> Numb
dot (a,b,c) (x,y,z)     = a*x + b*y + c*z
	
gateMinus		:: Numb -> Vec -> Vec
gateMinus ct		= f $ ct < 0 where
	f True		= minus
	f False		= id


modMinus		:: Numb -> Numb -> Vec -> Vec
modMinus cd ts 		= f $ cd `mod` ts where
	f 0		= minus
	f _		= id

modNot			:: Numb -> Numb -> Numb
modNot cd ts		= f $ cd `mod` ts where
	f 0		= 1
	f _		= 0