{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE EmptyDataDecls #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE StandaloneDeriving #-}
module Numeric.LAPACK.Matrix.Array.Private (
   Matrix(Array),
   ArrayMatrix,
   Array,
   OmniArray,
   PlainArray,

   Full,
   General,
   Tall,
   Wide,
   LiberalSquare,
   Square,
   SquareMeas,
   Quadratic,
   FullQuadratic,
   QuadraticMeas,

   plainShape,
   shape,
   extent,
   subBandsSingleton,
   superBandsSingleton,
   packTag,
   diagTag,

   asPacked,
   asUnpacked,
   requirePacking,

   reshape,
   mapShape,
   unwrap,
   toVector,
   fromVector,
   lift0,
   lift1,
   lift2,
   lift3,
   lift4,
   unlift1,
   unlift2,
   unliftRow,
   unliftColumn,
   unpackedToVector,
   liftUnpacked0,
   liftUnpacked1,
   liftUnpacked2,
   liftUnpacked3,
   liftOmni1,
   liftOmni2,

   Homogeneous, Scale, zero, negate, scaleReal, scale, scaleRealReal,
   order, forceOrder, adaptOrder,
   Additive, add,
   Subtractive, sub,

   MapExtent, mapExtent,
   ) where

import qualified Numeric.LAPACK.Matrix.Plain.Class as ArrClass
import qualified Numeric.LAPACK.Matrix.Type.Private as Matrix
import qualified Numeric.LAPACK.Matrix.BandedHermitian.Basic as BandedHermitian
import qualified Numeric.LAPACK.Matrix.Banded.Basic as Banded
import qualified Numeric.LAPACK.Matrix.Triangular.Basic as Triangular
import qualified Numeric.LAPACK.Matrix.Mosaic.Basic as Mosaic
import qualified Numeric.LAPACK.Matrix.Plain.Format as ArrFormat
import qualified Numeric.LAPACK.Matrix.Plain as Plain
import qualified Numeric.LAPACK.Matrix.Basic as Basic
import qualified Numeric.LAPACK.Matrix.Shape.Omni as Omni
import qualified Numeric.LAPACK.Matrix.Shape as MatrixShape
import qualified Numeric.LAPACK.Matrix.Layout.Private as Layout
import qualified Numeric.LAPACK.Matrix.Extent.Private as Extent
import qualified Numeric.LAPACK.Matrix.Extent.Strict as ExtentStrict
import qualified Numeric.LAPACK.Vector as Vector
import Numeric.LAPACK.Matrix.Shape.Omni (Omni, Arbitrary)
import Numeric.LAPACK.Matrix.Layout.Private
         (Filled, Bands, GetBands, Packed, Unpacked)
import Numeric.LAPACK.Matrix.Extent.Private (Extent, Shape, Size, Big, Small)
import Numeric.LAPACK.Matrix.Type.Private (Matrix)
import Numeric.LAPACK.Vector (Vector)
import Numeric.LAPACK.Scalar (RealOf, conjugate)

import qualified Type.Data.Num.Unary as Unary
import qualified Type.Data.Bool as TBool
import Type.Data.Bool (True)

import qualified Numeric.Netlib.Class as Class

import qualified Control.DeepSeq as DeepSeq

import qualified Data.Array.Comfort.Storable.Unchecked as Array
import qualified Data.Array.Comfort.Storable as CheckedArray
import qualified Data.Array.Comfort.Shape as Shape
import Data.Function.HT (Id)

import Foreign.Storable (Storable)

import GHC.Exts (Constraint)

import Prelude hiding (negate)


type OmniArray pack prop lower upper meas vert horiz height width a =
      Array.Array (Omni pack prop lower upper meas vert horiz height width) a

data Array pack property
data instance
      Matrix (Array pack prop) xl xu
         lower upper meas vert horiz height width a where
   Array ::
      OmniArray pack prop lower upper meas vert horiz height width a ->
      Matrix (Array pack prop) () () lower upper meas vert horiz height width a

deriving instance
   (Extent.Measure meas, Extent.C vert, Extent.C horiz,
    Shape.C height, Shape.C width, Storable a,
    Show height, Show width, Show a) =>
   Show (Matrix (Array pack prop) xl xu
            lower upper meas vert horiz height width a)

type ArrayMatrix pack property = Matrix (Array pack property) () ()
type UnpackedMatrix property = ArrayMatrix Unpacked property


type Full meas vert horiz height width =
         UnpackedMatrix Arbitrary Filled Filled meas vert horiz height width
type General height width = Full Size Big Big height width
type Tall height width = Full Size Big Small height width
type Wide height width = Full Size Small Big height width
type LiberalSquare height width = SquareMeas Size height width
type Square sh = SquareMeas Shape sh sh
type SquareMeas meas height width = Full meas Small Small height width

type Quadratic pack property lower upper sh =
         QuadraticMeas pack property lower upper Shape sh sh
type FullQuadratic pack property sh = Quadratic pack property Filled Filled sh

type QuadraticMeas pack property lower upper meas height width =
         ArrayMatrix pack property lower upper meas Small Small height width


instance Matrix.NFData (Array pack property) where
   rnf (Array arr) = DeepSeq.rnf arr

instance Matrix.Box (Array pack property) where
   type BoxExtra (Array pack property) extra = extra ~ ()
   extent (Array arr) = Omni.extent $ Array.shape arr

instance Matrix.Transpose (Array pack property) where
   type TransposeExtra (Array pack property) extra = extra ~ ()
   transpose a@(Array _) =
      case shape a of
         Omni.Full _ -> liftUnpacked1 Basic.transpose a
         Omni.UpperTriangular _ -> lift1 Mosaic.transpose a
         Omni.LowerTriangular _ -> lift1 Mosaic.transpose a
         Omni.Symmetric _ -> a
         Omni.Hermitian _ -> lift1 Vector.conjugate a
         Omni.Banded _ -> lift1 Banded.transpose a
         Omni.UnitBandedTriangular _ -> lift1 Banded.transpose a
         Omni.BandedHermitian _ -> lift1 Vector.conjugate a


asPacked ::
   Id (ArrayMatrix Packed property lower upper meas vert horiz height width a)
asPacked = id

asUnpacked ::
   Id (ArrayMatrix Unpacked property lower upper meas vert horiz height width a)
asUnpacked = id

requirePacking ::
   Layout.PackingSingleton pack ->
   Id (ArrayMatrix pack property lower upper meas vert horiz height width a)
requirePacking _ = id


plainShape ::
   (Omni.ToPlain pack property lower upper meas vert horiz height width) =>
   ArrayMatrix pack property lower upper meas vert horiz height width a ->
   Omni.Plain pack property lower upper meas vert horiz height width
plainShape = Omni.toPlain . shape

shape ::
   ArrayMatrix pack property lower upper meas vert horiz height width a ->
   Omni pack property lower upper meas vert horiz height width
shape (Array a) = Array.shape a

extent ::
   (Extent.Measure meas, Extent.C vert, Extent.C horiz) =>
   ArrayMatrix pack property lower upper meas vert horiz height width a ->
   Extent meas vert horiz height width
extent = Omni.extent . shape


subBandsSingleton ::
   (Unary.Natural sub) =>
   ArrayMatrix pack property
      (Bands sub) upper meas vert horiz height width a ->
   Unary.HeadSingleton sub
subBandsSingleton _ = Unary.headSingleton

superBandsSingleton ::
   (Unary.Natural super) =>
   ArrayMatrix pack property
      lower (Bands super) meas vert horiz height width a ->
   Unary.HeadSingleton super
superBandsSingleton _ = Unary.headSingleton


reshape ::
   (Extent.Measure measA, Extent.C vertA, Extent.C horizA) =>
   (Extent.Measure measB, Extent.C vertB, Extent.C horizB) =>
   (Shape.C heightA, Shape.C widthA) =>
   (Shape.C heightB, Shape.C widthB) =>
   Omni packB propB lowerB upperB measB vertB horizB heightB widthB ->
   ArrayMatrix packA propA lowerA upperA measA vertA horizA heightA widthA a ->
   ArrayMatrix packB propB lowerB upperB measB vertB horizB heightB widthB a
reshape = liftOmni1 . CheckedArray.reshape

mapShape ::
   (Extent.Measure measA, Extent.C vertA, Extent.C horizA) =>
   (Extent.Measure measB, Extent.C vertB, Extent.C horizB) =>
   (Shape.C heightA, Shape.C widthA) =>
   (Shape.C heightB, Shape.C widthB) =>
   (Omni packA propA lowerA upperA measA vertA horizA heightA widthA ->
    Omni packB propB lowerB upperB measB vertB horizB heightB widthB) ->
   ArrayMatrix packA propA lowerA upperA measA vertA horizA heightA widthA a ->
   ArrayMatrix packB propB lowerB upperB measB vertB horizB heightB widthB a
mapShape = liftOmni1 . CheckedArray.mapShape


type PlainArray pack prop lower upper meas vert horiz height width =
   Array.Array (Omni.Plain pack prop lower upper meas vert horiz height width)

toVector ::
   (Omni.ToPlain pack property lower upper meas vert horiz height width) =>
   ArrayMatrix pack property lower upper meas vert horiz height width a ->
   PlainArray pack property lower upper meas vert horiz height width a
toVector (Array a) = Array.mapShape Omni.toPlain a

unwrap ::
   ArrayMatrix pack property lower upper meas vert horiz height width a ->
   OmniArray pack property lower upper meas vert horiz height width a
unwrap (Array a) = a

fromVector ::
   (Omni.FromPlain pack prop lower upper meas vert horiz height width) =>
   (Omni.Plain pack prop lower upper meas vert horiz height width ~ shape) =>
   (Extent.Measure meas, Extent.C vert, Extent.C horiz) =>
   (Shape.C height, Shape.C width, Class.Floating a) =>
   Array.Array shape a ->
   ArrayMatrix pack prop lower upper meas vert horiz height width a
fromVector arr =
   let omni = Omni.fromPlain $ Array.shape arr
   in case ArrClass.check omni arr of
         Nothing -> Array $ Array.reshape omni arr
         Just msg -> error $ "Matrix.Array.fromVector: " ++ msg


{- |
'lift0' is a synonym for 'fromVector' but lacks the admissibility check.
You may thus fool the type tags.
This applies to the other lift functions, too.
-}
lift0 ::
   (Omni.FromPlain pack prop lower upper meas vert horiz height width) =>
   PlainArray pack prop lower upper meas vert horiz height width a ->
   ArrayMatrix pack prop lower upper meas vert horiz height width a
lift0 = Array . Array.mapShape Omni.fromPlain

lift1 ::
   (Omni.ToPlain packA propA lowerA upperA measA vertA horizA heightA widthA) =>
   (Omni.FromPlain packB propB lowerB upperB measB vertB horizB heightB widthB)
                                                                              =>
   (PlainArray packA propA lowerA upperA measA vertA horizA heightA widthA a ->
    PlainArray packB propB lowerB upperB measB vertB horizB heightB widthB b) ->
   ArrayMatrix packA propA lowerA upperA measA vertA horizA heightA widthA a ->
   ArrayMatrix packB propB lowerB upperB measB vertB horizB heightB widthB b
lift1 f = lift0 . f . toVector

lift2 ::
   (Omni.ToPlain packA propA lowerA upperA measA vertA horizA heightA widthA) =>
   (Omni.ToPlain packB propB lowerB upperB measB vertB horizB heightB widthB) =>
   (Omni.FromPlain packC propC lowerC upperC measC vertC horizC heightC widthC)
                                                                              =>
   (PlainArray packA propA lowerA upperA measA vertA horizA heightA widthA a ->
    PlainArray packB propB lowerB upperB measB vertB horizB heightB widthB b ->
    PlainArray packC propC lowerC upperC measC vertC horizC heightC widthC c) ->
   ArrayMatrix packA propA lowerA upperA measA vertA horizA heightA widthA a ->
   ArrayMatrix packB propB lowerB upperB measB vertB horizB heightB widthB b ->
   ArrayMatrix packC propC lowerC upperC measC vertC horizC heightC widthC c
lift2 f = lift1 . f . toVector

lift3 ::
   (Omni.ToPlain packA propA lowerA upperA measA vertA horizA heightA widthA) =>
   (Omni.ToPlain packB propB lowerB upperB measB vertB horizB heightB widthB) =>
   (Omni.ToPlain packC propC lowerC upperC measC vertC horizC heightC widthC) =>
   (Omni.FromPlain packD propD lowerD upperD measD vertD horizD heightD widthD)
                                                                              =>
   (PlainArray packA propA lowerA upperA measA vertA horizA heightA widthA a ->
    PlainArray packB propB lowerB upperB measB vertB horizB heightB widthB b ->
    PlainArray packC propC lowerC upperC measC vertC horizC heightC widthC c ->
    PlainArray packD propD lowerD upperD measD vertD horizD heightD widthD d) ->
   ArrayMatrix packA propA lowerA upperA measA vertA horizA heightA widthA a ->
   ArrayMatrix packB propB lowerB upperB measB vertB horizB heightB widthB b ->
   ArrayMatrix packC propC lowerC upperC measC vertC horizC heightC widthC c ->
   ArrayMatrix packD propD lowerD upperD measD vertD horizD heightD widthD d
lift3 f = lift2 . f . toVector

lift4 ::
   (Omni.ToPlain packA propA lowerA upperA measA vertA horizA heightA widthA) =>
   (Omni.ToPlain packB propB lowerB upperB measB vertB horizB heightB widthB) =>
   (Omni.ToPlain packC propC lowerC upperC measC vertC horizC heightC widthC) =>
   (Omni.ToPlain packD propD lowerD upperD measD vertD horizD heightD widthD) =>
   (Omni.FromPlain packE propE lowerE upperE measE vertE horizE heightE widthE)
                                                                              =>
   (PlainArray packA propA lowerA upperA measA vertA horizA heightA widthA a ->
    PlainArray packB propB lowerB upperB measB vertB horizB heightB widthB b ->
    PlainArray packC propC lowerC upperC measC vertC horizC heightC widthC c ->
    PlainArray packD propD lowerD upperD measD vertD horizD heightD widthD d ->
    PlainArray packE propE lowerE upperE measE vertE horizE heightE widthE e) ->
   ArrayMatrix packA propA lowerA upperA measA vertA horizA heightA widthA a ->
   ArrayMatrix packB propB lowerB upperB measB vertB horizB heightB widthB b ->
   ArrayMatrix packC propC lowerC upperC measC vertC horizC heightC widthC c ->
   ArrayMatrix packD propD lowerD upperD measD vertD horizD heightD widthD d ->
   ArrayMatrix packE propE lowerE upperE measE vertE horizE heightE widthE e
lift4 f = lift3 . f . toVector


unlift1 ::
   (Omni.FromPlain packA propA lowerA upperA measA vertA horizA heightA widthA)
                                                                              =>
   (Omni.ToPlain packB propB lowerB upperB measB vertB horizB heightB widthB) =>
   (ArrayMatrix packA propA lowerA upperA measA vertA horizA heightA widthA a ->
    ArrayMatrix packB propB lowerB upperB measB vertB horizB heightB widthB b) ->
   (PlainArray packA propA lowerA upperA measA vertA horizA heightA widthA a ->
    PlainArray packB propB lowerB upperB measB vertB horizB heightB widthB b)
unlift1 f = toVector . f . lift0

unlift2 ::
   (Omni.FromPlain packA propA lowerA upperA measA vertA horizA heightA widthA,
    Omni.FromPlain packB propB lowerB upperB measB vertB horizB heightB widthB)
                                                                              =>
   (Omni.ToPlain packC propC lowerC upperC measC vertC horizC heightC widthC) =>
   (ArrayMatrix packA propA lowerA upperA measA vertA horizA heightA widthA a ->
    ArrayMatrix packB propB lowerB upperB measB vertB horizB heightB widthB b ->
    ArrayMatrix packC propC lowerC upperC measC vertC horizC heightC widthC c) ->
   (PlainArray packA propA lowerA upperA measA vertA horizA heightA widthA a ->
    PlainArray packB propB lowerB upperB measB vertB horizB heightB widthB b ->
    PlainArray packC propC lowerC upperC measC vertC horizC heightC widthC c)
unlift2 f = unlift1 . f . lift0


type FullArray meas vert horiz height width =
      Array.Array (Layout.Full meas vert horiz height width)

unpackedToVector ::
   (Omni.Property property, Omni.Strip lower, Omni.Strip upper) =>
   UnpackedMatrix property lower upper meas vert horiz height width a ->
   FullArray meas vert horiz height width a
unpackedToVector = Array.mapShape Omni.toFull . unwrap

liftUnpacked0 ::
   (Omni.Property propertyA, Omni.Strip lowerA, Omni.Strip upperA) =>
   (FullArray measA vertA horizA heightA widthA a) ->
   UnpackedMatrix propertyA lowerA upperA measA vertA horizA heightA widthA a
liftUnpacked0 = Array . Array.mapShape Omni.fromFull

liftUnpacked1 ::
   (Omni.Property propertyA, Omni.Strip lowerA, Omni.Strip upperA) =>
   (Omni.Property propertyB, Omni.Strip lowerB, Omni.Strip upperB) =>
   (FullArray measA vertA horizA heightA widthA a ->
    FullArray measB vertB horizB heightB widthB b) ->
   UnpackedMatrix propertyA lowerA upperA measA vertA horizA heightA widthA a ->
   UnpackedMatrix propertyB lowerB upperB measB vertB horizB heightB widthB b
liftUnpacked1 f = liftUnpacked0 . f . unpackedToVector

liftUnpacked2 ::
   (Omni.Property propertyA, Omni.Strip lowerA, Omni.Strip upperA) =>
   (Omni.Property propertyB, Omni.Strip lowerB, Omni.Strip upperB) =>
   (Omni.Property propertyC, Omni.Strip lowerC, Omni.Strip upperC) =>
   (FullArray measA vertA horizA heightA widthA a ->
    FullArray measB vertB horizB heightB widthB b ->
    FullArray measC vertC horizC heightC widthC c) ->
   UnpackedMatrix propertyA lowerA upperA measA vertA horizA heightA widthA a ->
   UnpackedMatrix propertyB lowerB upperB measB vertB horizB heightB widthB b ->
   UnpackedMatrix propertyC lowerC upperC measC vertC horizC heightC widthC c
liftUnpacked2 f = liftUnpacked1 . f . unpackedToVector

liftUnpacked3 ::
   (Omni.Property propertyA, Omni.Strip lowerA, Omni.Strip upperA) =>
   (Omni.Property propertyB, Omni.Strip lowerB, Omni.Strip upperB) =>
   (Omni.Property propertyC, Omni.Strip lowerC, Omni.Strip upperC) =>
   (Omni.Property propertyD, Omni.Strip lowerD, Omni.Strip upperD) =>
   (FullArray measA vertA horizA heightA widthA a ->
    FullArray measB vertB horizB heightB widthB b ->
    FullArray measC vertC horizC heightC widthC c ->
    FullArray measD vertD horizD heightD widthD d) ->
   UnpackedMatrix propertyA lowerA upperA measA vertA horizA heightA widthA a ->
   UnpackedMatrix propertyB lowerB upperB measB vertB horizB heightB widthB b ->
   UnpackedMatrix propertyC lowerC upperC measC vertC horizC heightC widthC c ->
   UnpackedMatrix propertyD lowerD upperD measD vertD horizD heightD widthD d
liftUnpacked3 f = liftUnpacked2 . f . unpackedToVector


unliftRow ::
   Layout.Order ->
   (General () height0 a -> General () height1 b) ->
   Vector height0 a -> Vector height1 b
unliftRow order_ = Basic.unliftRow order_ . unlift1

unliftColumn ::
   Layout.Order ->
   (General height0 () a -> General height1 () b) ->
   Vector height0 a -> Vector height1 b
unliftColumn order_ = Basic.unliftColumn order_ . unlift1


instance Matrix.Format (Array pack property) where
   type FormatExtra (Array pack property) extra = ()
   format = Matrix.formatWithLayout

instance Matrix.Layout (Array pack property) where
   type LayoutExtra (Array pack property) extra = ()
   layout a@(Array _) =
      case shape a of
         Omni.Full fullShape ->
            ArrFormat.arrayFromList2 (Matrix.extent a) $
            ArrFormat.layoutFull $ Array.reshape fullShape $ unwrap a
         Omni.UpperTriangular _ ->
            ArrFormat.incompleteArrayFromList2 (Matrix.extent a) $
            ArrFormat.layoutTriangular $ toVector a
         Omni.LowerTriangular _ ->
            ArrFormat.incompleteArrayFromList2 (Matrix.extent a) $
            ArrFormat.layoutTriangular $ toVector a
         Omni.Symmetric _ ->
            ArrFormat.splitArrayFromList2 (Matrix.extent a) $
            ArrFormat.layoutMirrored id $ toVector a
         Omni.Hermitian _ ->
            ArrFormat.splitArrayFromList2 (Matrix.extent a) $
            ArrFormat.layoutMirrored conjugate $ toVector a
         Omni.Banded _ ->
            ArrFormat.incompleteArrayFromList2 (Matrix.extent a) $
            ArrFormat.layoutBanded $ toVector a
         Omni.UnitBandedTriangular _ ->
            ArrFormat.incompleteArrayFromList2 (Matrix.extent a) $
            ArrFormat.layoutBanded $ toVector a
         Omni.BandedHermitian _ ->
            ArrFormat.incompleteSplitArrayFromList2 (Matrix.extent a) $
            ArrFormat.layoutBandedHermitian $ toVector a


packTag ::
   (Layout.Packing pack) =>
   ArrayMatrix pack diag lower upper meas vert horiz height width a ->
   Layout.PackingSingleton pack
packTag _ = Layout.autoPacking

diagTag ::
   (Omni.TriDiag diag) =>
   ArrayMatrix pack diag lower upper meas vert horiz height width a ->
   Omni.DiagSingleton diag
diagTag _ = Omni.autoDiag

instance
   (Layout.Packing pack, Omni.TriDiag diag) =>
      Matrix.MultiplySame (Array pack diag) where
   type MultiplySameExtra (Array pack diag) extra = extra ~ ()
   multiplySame a =
      case Omni.powerSingleton $ shape a of
         Omni.PowerIdentity -> \b ->
            if Matrix.squareSize a == Matrix.squareSize b
               then b
               else error "multiplySame Identity: shape mismatch"
         Omni.PowerUpperTriangular ->
            lift2 (Triangular.multiply $ diagTag a) a
         Omni.PowerLowerTriangular ->
            lift2 (Triangular.multiply $ diagTag a) a
         Omni.PowerDiagonal ->
            case (diagTag a, shape a) of
               (MatrixShape.Unit, Omni.UnitBandedTriangular _) ->
                  lift2 Banded.multiply a
               (MatrixShape.Arbitrary, Omni.Banded _) ->
                  lift2 Banded.multiply a
               (_, Omni.Full _) -> liftUnpacked2 Basic.multiply a
         Omni.PowerSymmetric ->
            case diagTag a of _ -> error "Symmetric forbidden"
         Omni.PowerHermitian ->
            case diagTag a of _ -> error "Hermitian forbidden"
         Omni.PowerFull -> liftUnpacked2 Basic.multiply a


{-
ToDo: implement using Array.Omni.identityOrderAux.
however, we must prevent module cycle

instance
   (meas ~ Extent.Shape, vert ~ Extent.Small, horiz ~ Extent.Small,
    height ~ width, Shape.Static width) =>
      Matrix.StaticIdentity
         (Array pack property lower upper meas vert horiz height width) where
   staticIdentity =
      OmniBasic.identityOrder Layout.RowMajor Shape.static
-}


class (Omni.Property property) => Homogeneous property where
instance Homogeneous Arbitrary where
instance Homogeneous Omni.Symmetric where
instance (zero ~ True, neg ~ pos, TBool.C pos) =>
         Homogeneous (Omni.Hermitian neg zero pos) where

zero ::
   (Homogeneous property) =>
   (Extent.Measure meas, Extent.C vert, Extent.C horiz) =>
   (Shape.C height, Shape.C width, Class.Floating a) =>
   Omni pack property lower upper meas vert horiz height width ->
   ArrayMatrix pack property lower upper meas vert horiz height width a
zero = Array . Vector.zero

negate ::
   (Homogeneous property) =>
   (Extent.Measure meas, Extent.C vert, Extent.C horiz) =>
   (Shape.C height, Shape.C width, Class.Floating a) =>
   ArrayMatrix pack property lower upper meas vert horiz height width a ->
   ArrayMatrix pack property lower upper meas vert horiz height width a
negate (Array a) = Array $ Vector.negate a

scaleReal ::
   (Homogeneous property) =>
   (Extent.Measure meas, Extent.C vert, Extent.C horiz) =>
   (Shape.C height, Shape.C width, Class.Floating a) =>
   RealOf a ->
   ArrayMatrix pack property lower upper meas vert horiz height width a ->
   ArrayMatrix pack property lower upper meas vert horiz height width a
scaleReal a (Array v) = Array $ Vector.scaleReal a v

newtype ScaleReal f a = ScaleReal {getScaleReal :: a -> f a -> f a}

scaleRealReal ::
   (Homogeneous property) =>
   (Extent.Measure meas, Extent.C vert, Extent.C horiz) =>
   (Shape.C height, Shape.C width, Class.Real a) =>
   a ->
   ArrayMatrix pack property lower upper meas vert horiz height width a ->
   ArrayMatrix pack property lower upper meas vert horiz height width a
scaleRealReal =
   getScaleReal $ Class.switchReal (ScaleReal scaleReal) (ScaleReal scaleReal)


class (Homogeneous property) => Scale property where
instance Scale Arbitrary where
instance Scale Omni.Symmetric where

scale ::
   (Extent.Measure meas, Extent.C vert, Extent.C horiz) =>
   (Scale property, Shape.C height, Shape.C width, Class.Floating a) =>
   a ->
   ArrayMatrix pack property lower upper meas vert horiz height width a ->
   ArrayMatrix pack property lower upper meas vert horiz height width a
scale a (Array v) = Array $ Vector.scale a v


order ::
   ArrayMatrix pack property lower upper meas vert horiz height width a ->
   Layout.Order
order = Omni.order . shape

forceOrder ::
   (Extent.Measure meas, Extent.C vert, Extent.C horiz) =>
   (Shape.C height, Shape.C width, Class.Floating a) =>
   Layout.Order ->
   ArrayMatrix pack property lower upper meas vert horiz height width a ->
   ArrayMatrix pack property lower upper meas vert horiz height width a
forceOrder order_ a =
   case shape a of
      Omni.UpperTriangular _ -> lift1 (Triangular.forceOrder order_) a
      Omni.LowerTriangular _ -> lift1 (Triangular.forceOrder order_) a
      Omni.Full _ -> liftUnpacked1 (Basic.forceOrder order_) a
      Omni.Symmetric _ -> lift1 (Triangular.forceOrder order_) a
      Omni.Hermitian _ -> lift1 (Triangular.forceOrder order_) a
      Omni.Banded _ -> lift1 (Banded.forceOrder order_) a
      Omni.BandedHermitian _ -> lift1 (BandedHermitian.forceOrder order_) a
      Omni.UnitBandedTriangular _ -> lift1 (Banded.forceOrder order_) a

{- |
@adaptOrder x y@ contains the data of @y@ with the layout of @x@.
-}
adaptOrder ::
   (Extent.Measure meas, Extent.C vert, Extent.C horiz) =>
   (Shape.C height, Shape.C width, Class.Floating a) =>
   ArrayMatrix pack property lower upper meas vert horiz height width a ->
   ArrayMatrix pack property lower upper meas vert horiz height width a ->
   ArrayMatrix pack property lower upper meas vert horiz height width a
adaptOrder = forceOrder . order


class (Omni.Property property) => Additive property where
instance Additive Arbitrary where
instance Additive Omni.Symmetric where
instance (TBool.C neg, TBool.C zero, TBool.C pos) =>
            Additive (Omni.Hermitian neg zero pos) where

class (Additive property) => Subtractive property where
instance Subtractive Arbitrary where
instance Subtractive Omni.Symmetric where
instance (TBool.C neg, TBool.C zero, neg ~ pos) =>
            Subtractive (Omni.Hermitian neg zero pos) where

infixl 6 `add`, `sub`

add ::
   (Extent.Measure meas, Extent.C vert, Extent.C horiz) =>
   (Additive property,
    Shape.C height, Eq height, Shape.C width, Eq width, Class.Floating a) =>
   ArrayMatrix pack property lower upper meas vert horiz height width a ->
   ArrayMatrix pack property lower upper meas vert horiz height width a ->
   ArrayMatrix pack property lower upper meas vert horiz height width a
add a b = liftOmni2 Vector.add (adaptOrder b a) b

sub ::
   (Extent.Measure meas, Extent.C vert, Extent.C horiz) =>
   (Subtractive property,
    Shape.C height, Eq height, Shape.C width, Eq width, Class.Floating a) =>
   ArrayMatrix pack property lower upper meas vert horiz height width a ->
   ArrayMatrix pack property lower upper meas vert horiz height width a ->
   ArrayMatrix pack property lower upper meas vert horiz height width a
sub a b = liftOmni2 Vector.sub (adaptOrder b a) b


liftOmni1 ::
   (OmniArray packA propA lowerA upperA measA vertA horizA heightA widthA a ->
    OmniArray packB propB lowerB upperB measB vertB horizB heightB widthB b)
   ->
   ArrayMatrix packA propA lowerA upperA measA vertA horizA heightA widthA a ->
   ArrayMatrix packB propB lowerB upperB measB vertB horizB heightB widthB b
liftOmni1 f (Array a) = Array $ f a

liftOmni2 ::
   (OmniArray packA propA lowerA upperA measA vertA horizA heightA widthA a ->
    OmniArray packB propB lowerB upperB measB vertB horizB heightB widthB b ->
    OmniArray packC propC lowerC upperC measC vertC horizC heightC widthC c)
   ->
   ArrayMatrix packA propA lowerA upperA measA vertA horizA heightA widthA a ->
   ArrayMatrix packB propB lowerB upperB measB vertB horizB heightB widthB b ->
   ArrayMatrix packC propC lowerC upperC measC vertC horizC heightC widthC c
liftOmni2 f (Array a) (Array b) = Array $ f a b



instance Matrix.ToQuadratic (Array pack property) where
   heightToQuadratic a@(Array _) =
      case shape a of
         Omni.Full _ ->
            ($ a) $ liftUnpacked1 $ Basic.mapExtent $
               Extent.square . Extent.height
         Omni.UpperTriangular _ -> a
         Omni.LowerTriangular _ -> a
         Omni.Symmetric _ -> a
         Omni.Hermitian _ -> a
         Omni.Banded _ ->
            ($ a) $ lift1 $ Banded.mapExtentSizes $ Extent.square . Extent.height
         Omni.UnitBandedTriangular _ -> a
         Omni.BandedHermitian _ -> a
   widthToQuadratic a@(Array _) =
      case shape a of
         Omni.Full _ ->
            ($ a) $ liftUnpacked1 $ Basic.mapExtent $
               Extent.square . Extent.width
         Omni.UpperTriangular _ -> a
         Omni.LowerTriangular _ -> a
         Omni.Symmetric _ -> a
         Omni.Hermitian _ -> a
         Omni.Banded _ ->
            ($ a) $ lift1 $ Banded.mapExtentSizes $ Extent.square . Extent.width
         Omni.UnitBandedTriangular _ -> a
         Omni.BandedHermitian _ -> a


instance
   (MapExtent pack, property ~ Arbitrary) =>
      Matrix.MapExtent (Array pack property) where
   type MapExtentExtra (Array pack property) extra = extra ~ ()
   type MapExtentStrip (Array pack property) strip = MapExtentStrip pack strip
   mapExtent = mapExtent

class MapExtent pack where
   type MapExtentStrip pack strip :: Constraint
   mapExtent ::
      (property ~ Arbitrary) =>
      (Extent.Measure measA, Extent.C vertA, Extent.C horizA) =>
      (Extent.Measure measB, Extent.C vertB, Extent.C horizB) =>
      (MapExtentStrip pack lower, MapExtentStrip pack upper) =>
      ExtentStrict.Map measA vertA horizA measB vertB horizB height width ->
      ArrayMatrix pack property lower upper measA vertA horizA height width a ->
      ArrayMatrix pack property lower upper measB vertB horizB height width a

instance MapExtent Unpacked where
   type MapExtentStrip Unpacked strip = strip ~ Filled
   mapExtent = lift1 . Plain.mapExtent . ExtentStrict.apply

instance MapExtent Packed where
   type MapExtentStrip Packed strip =
            (strip ~ Bands (GetBands strip),
             Unary.Natural (GetBands strip))
   mapExtent = lift1 . Banded.mapExtent . ExtentStrict.apply