{-|
  Mapping of basic Matlab types to Haskell types.
-}
module Foreign.Matlab.Types (
    -- * Representations of Matlab types
    -- |Most types are simple aliases of Haskell types, given simply to identify them and provide a consistent naming scheme.

    MIO,

    MXClass(..),
    MChar,
    MLogical,
    MScalar,

    MNumeric,
    -- * Numeric types
    MDouble,
    MSingle,
    MInt8,
    MInt16,
    MInt32,
    MInt64,
    MUint8,
    MUint16,
    MUint32,
    MUint64,
    MComplex,

    -- * Array indexing
    MSize, MSubs,
    MIndex(..),
    mStart, mOffset,
    mSizeRange, mRangeSize,
    normMSize, realMSize,

    -- * Opaque types
    MXArray,
    MAnyArray,
    MCell(MCell), mCell,
    MStruct,
    MFun
  ) where

import Data.Complex
import Data.Ix
import Foreign.Matlab.Internal

-- |The class of types which are simple Matlab scalars and can be array elements
class MScalar a
-- |The class of types which Matlab consisters \"numeric\"
class (MScalar a, Num a) => MNumeric a

instance MScalar MChar
instance MScalar MLogical
instance MScalar MDouble
instance MScalar MSingle
instance MScalar MInt8
instance MScalar MInt16
instance MScalar MInt32
instance MScalar MInt64
instance MScalar MUint8
instance MScalar MUint16
instance MScalar MUint32
instance MScalar MUint64
instance MNumeric a => MScalar (MComplex a)

instance MNumeric MDouble
instance MNumeric MSingle
instance MNumeric MInt8
instance MNumeric MInt16
instance MNumeric MInt32
instance MNumeric MInt64
instance MNumeric MUint8
instance MNumeric MUint16
instance MNumeric MUint32
instance MNumeric MUint64

-- |Complex numeric types.  Unfortunately, 'Complex' only applies to 'RealFloat' types, whereas Matlab allows any numeric type, so some types are (currently) unaccessable.
type MComplex = Complex

-- |The type of array sizes, which are the column-major lengths of each dimensions
type MSize = [Int]
-- |The type of array index subscripts, which are the column-major, 0-based indices in each dimension with normal Matlab semantics (flatten along last dimension)
type MSubs = [Int]
-- |Ways to index an array.  Using Matlab semantics, a singleton MSubs [n] is equivalent to a raw 0-based offset (MOffset n).
newtype MIndex = MSubs MSubs
    -- MOffset Int -- ^0-based offset from the beginning of the array (equivalent to singleton MSubs)

-- |First index in array
mStart :: MIndex
mStart = MSubs []

-- |Raw, 0-based array offset index
mOffset :: Int -> MIndex
mOffset i = MSubs [i]

instance Show MIndex where
  showsPrec _ (MSubs []) = id
  showsPrec _ (MSubs l) = showChar '(' . foldr1 (\s r -> s . showChar ',' . r) (map shows l) . showChar ')'
instance Eq MIndex where
  MSubs a == MSubs b = eq a b where
    eq [] [] = True
    eq (x:a) (y:b) = (x == y) && eq a b
    eq [] b = eq [0] b
    eq a [] = eq a [0]
instance Ord MIndex where
  compare (MSubs a) (MSubs b) = cmp a b where
    cmp [] [] = EQ
    cmp (x:a) (y:b) = case cmp a b of { EQ -> compare x y ; r -> r }
    cmp [] b = cmp [0] b
    cmp a [] = cmp a [0]
instance Ix MIndex where
  range (MSubs a, MSubs b) = map MSubs $ rng a b where
    rng [] [] = [[]]
    rng (x:a) (y:b) = concatMap (\l -> map (:l) [x..y]) $ rng a b
    rng [] b = rng [0] b
    rng _ _ = error "MIndex.range: length mismatch"
  index (MSubs a, MSubs b) (MSubs l) = idx a b l where
    idx _ _ [] = 0
    idx (x:a) (y:b) (i:l) = i-x+((y-x+1)*idx a b l)
    idx [] [] (0:l) = idx [] [] l
    idx [] b l = idx [0] b l
    idx _ _ _ = error "MIndex.index: length mismatch"
  inRange (MSubs a, MSubs b) (MSubs l) = inr a b l where
    inr _ _ [] = True
    inr a@(x:_) b@(_:_) [i] = i >= x && i-x < rangeSize (MSubs a, MSubs b)
    inr (x:a) (y:b) (i:l) = i >= x && i <= y && inr a b l
    inr [] [] (0:l) = inr [] [] l
    inr [] b l = inr [0] b l
    inr _ _ _ = error "MIndex.inRange: length mismatch"
  rangeSize (MSubs a, MSubs b) = rsz a b where
    rsz [] b = product $ map succ b
    rsz (x:a) (y:b) = (y-x+1)*rsz a b
    rsz _ _ = error "MIndex.rangeSize: length mismatch"

-- |Convert an array size to an index range, which will be of the form ((0,0,0...),(i-1,j-1,k-1,...))
mSizeRange :: MSize -> (MIndex,MIndex)
mSizeRange l = (MSubs [], MSubs $ map pred $ normMSize l)

-- |Convert an index range to an array size
mRangeSize :: (MIndex,MIndex) -> MSize
mRangeSize (MSubs [], MSubs l) = map succ l
mRangeSize _ = error "mRangeSize: invalid lower bound"

-- |Get the size form that Matlab likes (@length (realMSize s) >= 2@)
realMSize :: MSize -> MSize
realMSize [] = [1,1]
realMSize [n] = [n,1]
realMSize s = s

-- |Get a more useful size form (no trailing singletons)
normMSize :: MSize -> MSize
normMSize [] = []
normMSize (1:l) = case normMSize l of { [] -> [] ; l -> (1:l) }
normMSize (x:l) = x : normMSize l