{-# language TypeFamilies, FlexibleInstances, MultiParamTypeClasses #-}
module Data.Sparse.Internal.CSC where

import Control.Monad (forM_, when)
import Control.Monad.Primitive (PrimMonad(..), PrimState(..))

import qualified Data.Graph as G
import qualified Data.Vector as V
import qualified Data.Vector.Mutable as VM

-- import Data.Sparse.Types
import Data.Sparse.Internal.SVector
import Data.Sparse.Internal.Utils
import Numeric.LinearAlgebra.Class


data CscMatrix a =
  CscM {
      cscNrows :: {-# UNPACK #-} !Int,
      cscNcols :: {-# UNPACK #-} !Int,
      cscNz :: {-# UNPACK #-} !Int,
      cscRowIx :: V.Vector Int,
      cscColPtr :: V.Vector Int,
      cscVal :: V.Vector a} deriving Eq

instance Functor CscMatrix where
  fmap f (CscM m n nz cc rp x) = CscM m n nz cc rp (fmap f x)

instance Foldable CscMatrix where
  foldr f z cm = foldr f z (cscVal cm)

instance Show a => Show (CscMatrix a) where
  show m'@(CscM m n nz cix rp x) = szs where
    szs = unwords ["CSC (",show m, "x", show n,"),",show nz, "NZ ( sparsity",show (spy m'),"), row indices:",show cix,", column pointers:", show rp,", data:",show x]

instance FiniteDim (CscMatrix a) where
  type FDSize (CscMatrix a) = (Int, Int)
  dim m = (cscNrows m, cscNcols m)

instance HasData (CscMatrix a) where
  nnz = cscNz
  
instance Sparse (CscMatrix a) where
  spy m = fromIntegral (nnz m) / fromIntegral (cscNrows m * cscNcols m)

  


-- * Creation
-- | O(N log N) : Copy a Vector containing (row index, column index, entry) into a CSC structure. Sorts the Vector by row columns ( O(log N) ), unzips row indices and data ( O(N) ) and generates the column pointer vector ( 2 O(N) passes )
toCSC :: Int -> Int -> V.Vector (Int, Int, a) -> CscMatrix a
toCSC m n ijxv = CscM m n nz rix crp x where
  nz = V.length x
  (rix, cix, x) = V.unzip3 (sortWith snd3 ijxv)  -- sort by columns
  crp = csPtrV (==) m cix

-- | O(N) : Rebuilds the (row, column, entry) Vector from the CSC representation. 
fromCSC :: CscMatrix a -> V.Vector (Int, Int, a)
fromCSC mc = V.zip3 ii jj xx where (ii,jj,xx) = fromCSC0 mc

fromCSC0 :: CscMatrix a -> (V.Vector Int, V.Vector Int, V.Vector a)
fromCSC0 mc = (rowIx, cols, cscVal mc) where
  (_, n) = dim mc
  rowIx = cscRowIx mc
  l = length rowIx
  cp = cscColPtr mc
  cols = V.create $ do
    colv <- VM.replicate l 0
    forM_ [0 .. n-1] (\i -> go colv i 0)
    return colv
  go :: PrimMonad m => VM.MVector (PrimState m) Int -> Int -> Int -> m ()  
  go vm irow j = when (j < nj) $ do
                          VM.write vm (j + jmin) irow
                          go vm irow (succ j) where
    jmin = cp V.! irow
    jmax = cp V.! (irow + 1)
    nj = jmax - jmin


-- ** Extract a column
-- | O(1) : extract a column from the CSC matrix.
extractColCSC :: CscMatrix a -> Int -> SVector a
extractColCSC (CscM m _ _ rix cp x) jcol = SV m ixs vals where
  jmin = cp V.! jcol
  jmax = cp V.! (jcol + 1)
  ixs = V.slice jmin (jmax - jmin) rix
  vals = V.slice jmin (jmax - jmin) x

-- ** Extract the diagonal element, if this exists (NB this only holds for a lower triangular matrix)
extractDiagSubdiagCSC :: CscMatrix a -> Int -> Maybe (a, SVector a)
extractDiagSubdiagCSC cscm j
  | V.length ixs > 1 && j == V.head ixs = Just (diagEl, subdiagVec)
  | otherwise = Nothing where
      (SV m ixs vals) = extractColCSC cscm j
      diagEl = V.head vals
      subdiagVec = SV (m-1) (V.tail ixs) (V.tail vals) 






-- | O(N log N) : Transpose CSC matrix
transposeCSC :: CscMatrix a -> CscMatrix a
transposeCSC mm = toCSC n m $ V.zip3 jj ii xx where
  (m,n) = dim mm
  (ii, jj, xx) = fromCSC0 mm


-- * Helpers

cscToGraph :: CscMatrix a -> G.Graph
cscToGraph ll = G.buildG (0, m-1) $ V.toList (V.zip ill jll) -- graph of L
  where
   (m, _) = dim ll -- dimensions of L = bounds of G(L)
   (ill, jll, _) = fromCSC0 ll




-- -- example data

-- -- row = np.array([0, 0, 1, 2, 2, 2])
-- -- col = np.array([0, 2, 2, 0, 1, 2])
-- -- data = np.array([1, 2, 3, 4, 5, 6])
