-- |
-- Module    : Statistics.Matrix.Types
-- Copyright : 2014 Bryan O'Sullivan
-- License   : BSD3
--
-- Basic matrix operations.
--
-- There isn't a widely used matrix package for Haskell yet, so
-- we implement the necessary minimum here.

module Statistics.Matrix.Types
    (
      Vector
    , MVector
    , Matrix(..)
    , MMatrix(..)
    , debug
    ) where

import Data.Char (isSpace)
import Numeric (showFFloat)
import qualified Data.Vector.Unboxed as U
import qualified Data.Vector.Unboxed.Mutable as M

type Vector = U.Vector Double
type MVector s = M.MVector s Double

-- | Two-dimensional matrix, stored in row-major order.
data Matrix = Matrix {
      rows     :: {-# UNPACK #-} !Int -- ^ Rows of matrix.
    , cols     :: {-# UNPACK #-} !Int -- ^ Columns of matrix.
    , exponent :: {-# UNPACK #-} !Int
      -- ^ In order to avoid overflows during matrix multiplication, a
      -- large exponent is stored separately.
    , _vector  :: !Vector  -- ^ Matrix data.
    } deriving (Eq)

-- | Two-dimensional mutable matrix, stored in row-major order.
data MMatrix s = MMatrix
                 {-# UNPACK #-} !Int
                 {-# UNPACK #-} !Int
                 {-# UNPACK #-} !Int
                 !(MVector s)

-- The Show instance is useful only for debugging.
instance Show Matrix where
    show = debug

debug :: Matrix -> String
debug (Matrix r c _ vs) = unlines $ zipWith (++) (hdr0 : repeat hdr) rrows
  where
    rrows         = map (cleanEnd . unwords) . split $ zipWith (++) ldone tdone
    hdr0          = show (r,c) ++ " "
    hdr           = replicate (length hdr0) ' '
    pad plus k xs = replicate (k - length xs) ' ' `plus` xs
    ldone         = map (pad (++) (longest lstr)) lstr
    tdone         = map (pad (flip (++)) (longest tstr)) tstr
    (lstr, tstr)  = unzip . map (break (=='.') . render) . U.toList $ vs
    longest       = maximum . map length
    render k      = reverse . dropWhile (=='.') . dropWhile (=='0') . reverse .
                    showFFloat (Just 4) k $ ""
    split []      = []
    split xs      = i : split rest where (i, rest) = splitAt c xs
    cleanEnd      = reverse . dropWhile isSpace . reverse