{-# LANGUAGE TypeFamilies #-}
module Numeric.LAPACK.Matrix.Banded.Linear (
   solve,
   solveColumnMajor,
   determinant,
   ) where

import qualified Numeric.LAPACK.Matrix.Banded.Basic as Banded
import qualified Numeric.LAPACK.Matrix.Shape.Private as MatrixShape
import qualified Numeric.LAPACK.Matrix.Extent.Private as Extent
import qualified Numeric.LAPACK.Permutation.Private as Perm
import qualified Numeric.LAPACK.Private as Private
import Numeric.LAPACK.Linear.Private (solver, withDeterminantInfo, withInfo)
import Numeric.LAPACK.Matrix.Shape.Private
         (Order(RowMajor,ColumnMajor), transposeFromOrder)
import Numeric.LAPACK.Matrix.Private (Full)
import Numeric.LAPACK.Private (copySubMatrix)

import qualified Numeric.LAPACK.FFI.Generic as LapackGen
import qualified Numeric.Netlib.Utility as Call
import qualified Numeric.Netlib.Class as Class

import qualified Type.Data.Num.Unary as Unary
import Type.Data.Num (integralFromProxy)

import qualified Data.Array.Comfort.Shape as Shape
import Data.Array.Comfort.Storable.Unchecked (Array(Array))

import System.IO.Unsafe (unsafePerformIO)

import Foreign.Marshal.Array (peekArray, advancePtr)
import Foreign.ForeignPtr (withForeignPtr)

import Control.Monad.Trans.Cont (ContT(ContT), evalContT)
import Control.Monad.IO.Class (liftIO)


solve ::
   (Unary.Natural sub, Unary.Natural super, Extent.C vert, Extent.C horiz,
    Shape.C sh, Eq sh, Shape.C nrhs, Class.Floating a) =>
   Banded.Square sub super sh a ->
   Full vert horiz sh nrhs a -> Full vert horiz sh nrhs a
solve (Array (MatrixShape.Banded numOff order extent) a) =
   solver "Banded.solve" (Extent.squareSize extent) $
         \n nPtr nrhsPtr xPtr ldxPtr -> do
      let (kl,ku) = MatrixShape.numOffDiagonals order numOff
      let k = kl+1+ku
      let ldab = kl+k
      transPtr <- Call.char $ transposeFromOrder order
      klPtr <- Call.cint kl
      kuPtr <- Call.cint ku
      aPtr <- ContT $ withForeignPtr a
      abPtr <- Call.allocaArray (n*ldab)
      ldabPtr <- Call.leadingDim ldab
      ipivPtr <- Call.allocaArray n
      liftIO $ do
         copySubMatrix k n k aPtr ldab (advancePtr abPtr kl)
         withInfo "gbtrf" $
            LapackGen.gbtrf nPtr nPtr klPtr kuPtr abPtr ldabPtr ipivPtr
         withInfo "gbtrs" $
            LapackGen.gbtrs transPtr nPtr klPtr kuPtr nrhsPtr
               abPtr ldabPtr ipivPtr xPtr ldxPtr

solveColumnMajor ::
   (Unary.Natural sub, Unary.Natural super, Extent.C vert, Extent.C horiz,
    Shape.C sh, Eq sh, Shape.C nrhs, Class.Floating a) =>
   Banded.Square sub super sh a ->
   Full vert horiz sh nrhs a -> Full vert horiz sh nrhs a
solveColumnMajor
      (Array (MatrixShape.Banded (sub,super) ColumnMajor extent) a) =
   solver "Banded.solve" (Extent.squareSize extent) $
         \n nPtr nrhsPtr xPtr ldxPtr -> do
      let kl = integralFromProxy sub
      let ku = integralFromProxy super
      let k = kl+1+ku
      let ldab = kl+k
      klPtr <- Call.cint kl
      kuPtr <- Call.cint ku
      aPtr <- ContT $ withForeignPtr a
      abPtr <- Call.allocaArray (n*ldab)
      ldabPtr <- Call.leadingDim ldab
      ipivPtr <- Call.allocaArray n
      liftIO $ do
         copySubMatrix k n k aPtr ldab (advancePtr abPtr kl)
         withInfo "gbsv" $
            LapackGen.gbsv nPtr klPtr kuPtr nrhsPtr
               abPtr ldabPtr ipivPtr xPtr ldxPtr
solveColumnMajor (Array (MatrixShape.Banded _ RowMajor _) _) =
   error "Linear.Banded.solveColumnMajor: RowMajor intentionally unimplemented"

determinant ::
   (Unary.Natural sub, Unary.Natural super, Shape.C sh, Class.Floating a) =>
   Banded.Square sub super sh a -> a
determinant (Array (MatrixShape.Banded numOff order extent) a) =
      unsafePerformIO $ do
   let n = Shape.size $ Extent.squareSize extent
   evalContT $ do
      let (kl,ku) = MatrixShape.numOffDiagonals order numOff
      let k = kl+1+ku
      let ldab = kl+k
      nPtr <- Call.cint n
      klPtr <- Call.cint kl
      kuPtr <- Call.cint ku
      aPtr <- ContT $ withForeignPtr a
      abPtr <- Call.allocaArray (n*ldab)
      ldabPtr <- Call.leadingDim ldab
      ipivPtr <- Call.allocaArray n
      liftIO $ do
         copySubMatrix k n k aPtr ldab (advancePtr abPtr kl)
         withDeterminantInfo "gbtrf"
            (LapackGen.gbtrf nPtr nPtr klPtr kuPtr abPtr ldabPtr ipivPtr)
            (do
               det <- Private.product n (advancePtr abPtr (kl+ku)) ldab
               ipiv <- peekArray n ipivPtr
               return $ Perm.condNegate ipiv det)