{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE EmptyDataDecls #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE Rank2Types #-}
module Numeric.LAPACK.Matrix.Shape.Omni (
   Omni(..),
   Unit,
   Arbitrary,
   Symmetric,
   Hermitian,
   HermitianUnknownDefiniteness,
   HermitianPositiveDefinite,
   HermitianPositiveSemidefinite,
   HermitianNegativeDefinite,
   HermitianNegativeSemidefinite,
   hermitianSet,
   TriDiag(switchTriDiag),
   DiagSingleton(..),
   autoDiag,
   charFromTriDiag,
   packTag,
   Property,
   property,
   PropertySingleton(..),
   propertySingleton,
   Strip(..),
   strips,
   StripSingleton(..),
   stripSingleton,
   PowerStrip(..),
   PowerStripSingleton(..),
   powerStripSingleton,
   powerStrips,
   BandedTriangular,
   BandedTriangularSingleton(BandedLower, BandedUpper, BandedDiagonal),
   bandedTriangularSingleton,
   extent,
   height, width, squareSize,
   mapHeight, mapWidth, mapSquareSize,
   order,
   transpose,
   Cons(cons),
   Plain,
   ToPlain, toPlain,
   FromPlain, fromPlain,
   toFull, fromFull,
   toBanded, toBandedHermitian,
   MultipliedBands,
   MultipliedStrip,
   MultipliedProperty,
   UnitIfTriangular,
   MergeUnit,
   Quadratic,
   quadratic,
   uncheckedDiagonal,
   Power(..),
   powerSingleton,
   ) where

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 ExtentS
import Numeric.LAPACK.Matrix.Extent.Private (Extent, Small, Shape, Size)
import Numeric.LAPACK.Matrix.Layout.Private
         (Bands, Empty, Filled, Packed, Unpacked)

import qualified Data.Array.Comfort.Shape as Shape

import qualified Type.Data.Num.Unary as Unary
import qualified Type.Data.Bool as TBool
import Type.Data.Num.Unary.Literal (U0)
import Type.Data.Bool (False, True)
import Type.Base.Proxy (Proxy(Proxy))

import qualified Control.DeepSeq as DeepSeq



data Unit
data Arbitrary

class (Property diag) => TriDiag diag where
   switchTriDiag :: f Unit -> f Arbitrary -> f diag
instance TriDiag Unit where switchTriDiag f _ = f
instance TriDiag Arbitrary where switchTriDiag _ f = f

autoDiag :: TriDiag diag => DiagSingleton diag
autoDiag = switchTriDiag Unit Arbitrary

charFromTriDiag :: TriDiag diag => DiagSingleton diag -> Char
charFromTriDiag diag = case diag of Unit -> 'U'; Arbitrary -> 'N'


data DiagSingleton diag where
   Unit :: DiagSingleton Unit
   Arbitrary :: DiagSingleton Arbitrary

instance Eq (DiagSingleton diag) where
   Unit == Unit  =  True
   Arbitrary == Arbitrary  =  True

instance Show (DiagSingleton diag) where
   show Unit = "Unit"
   show Arbitrary = "Arbitrary"

instance DeepSeq.NFData (DiagSingleton diag) where
   rnf Unit = ()
   rnf Arbitrary = ()


data Symmetric

data Hermitian neg zero pos
type HermitianUnknownDefiniteness  = Hermitian True  True  True
type HermitianPositiveDefinite     = Hermitian False False True
type HermitianPositiveSemidefinite = Hermitian False True  True
type HermitianNegativeDefinite     = Hermitian True  False False
type HermitianNegativeSemidefinite = Hermitian True  True  False

{- |
Impossible:

> instance Definiteness False False False where
> instance Definiteness True  False True  where

The last one is impossible for this reason:

Given @x@ and @y@ with @x^T*A*x < 0@ and @y^T*A*y > 0@.
Because of the intermediate value theorem
there must be a @k@ from @[0,1]@ with
@z = k*x + (1-k)*y@ and @z^T*A*z = 0@.
-}
class Definiteness neg zero pos where
instance Definiteness True  True  True  where
instance Definiteness True  True  False where
instance Definiteness False True  True  where
instance Definiteness True  False False where
instance Definiteness False True  False where
instance Definiteness False False True  where

hermitianSet ::
   (TBool.C neg, TBool.C zero, TBool.C pos) =>
   Omni pack (Hermitian neg zero pos)
      lower upper meas vert horiz height width ->
   (TBool.Singleton neg, TBool.Singleton zero, TBool.Singleton pos)
hermitianSet _ = (TBool.singleton, TBool.singleton, TBool.singleton)



class Property property where
   switchProperty ::
      f Arbitrary ->
      f Unit ->
      f Symmetric ->
      (forall neg zero pos.
         (TBool.C neg, TBool.C zero, TBool.C pos) =>
         f (Hermitian neg zero pos)) ->
      f property

instance Property Arbitrary where switchProperty f _ _ _ = f
instance Property Unit where switchProperty _ f _ _ = f
instance Property Symmetric where switchProperty _ _ f _ = f
instance
   (TBool.C neg, TBool.C zero, TBool.C pos) =>
      Property (Hermitian neg zero pos) where
   switchProperty _ _ _ f = f

data PropertySingleton property where
   PropArbitrary :: PropertySingleton Arbitrary
   PropUnit      :: PropertySingleton Unit
   PropSymmetric :: PropertySingleton Symmetric
   PropHermitian ::
      (TBool.C neg, TBool.C zero, TBool.C pos) =>
      PropertySingleton (Hermitian neg zero pos)

propertySingleton ::
   (Property property) => PropertySingleton property
propertySingleton =
   switchProperty PropArbitrary PropUnit PropSymmetric PropHermitian

property ::
   (Property property) =>
   Omni pack property lower upper meas vert horiz height width ->
   PropertySingleton property
property _ = propertySingleton


class
   (MultipliedBands c Filled ~ Filled, MultipliedBands c Empty ~ c) =>
      Strip c where
   switchStrip ::
      (forall offDiag. Unary.Natural offDiag => f (Bands offDiag)) ->
      f Filled ->
      f c
instance (Unary.Natural offDiag) => Strip (Bands offDiag) where
   switchStrip f _ = f
instance Strip Filled where
   switchStrip _ f = f


data StripSingleton c where
   StripBands ::
      (Unary.Natural offDiag) =>
      Unary.HeadSingleton offDiag -> StripSingleton (Bands offDiag)
   StripFilled :: StripSingleton Filled

stripSingleton :: (Strip c) => StripSingleton c
stripSingleton = switchStrip (StripBands Unary.headSingleton) StripFilled

strips ::
   (Strip lower, Strip upper) =>
   Omni pack property lower upper meas vert horiz height width ->
   (StripSingleton lower, StripSingleton upper)
strips _ = (stripSingleton, stripSingleton)



{- |
'PowerStrip' is either 'Empty' or 'Filled'.
These are the 'Strip's that are preserved in matrix powers.

Pun intended.
-}
class (Strip c) => PowerStrip c where
   switchPowerStrip :: f Empty -> f Filled -> f c
instance (offDiag ~ U0) => PowerStrip (Bands offDiag) where
   switchPowerStrip f _ = f
instance PowerStrip Filled where
   switchPowerStrip _ f = f

data PowerStripSingleton c where
   Empty :: PowerStripSingleton Empty
   Filled :: PowerStripSingleton Filled

powerStripSingleton :: (PowerStrip c) => PowerStripSingleton c
powerStripSingleton = switchPowerStrip Empty Filled

powerStrips ::
   (PowerStrip lower, PowerStrip upper) =>
   Omni pack property lower upper meas vert horiz height width ->
   (PowerStripSingleton lower, PowerStripSingleton upper)
powerStrips _ = (powerStripSingleton, powerStripSingleton)


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


type PowerQuadratic pack property lower upper sh =
      Power pack property lower upper Shape Small Small sh sh

data Power pack property lower upper meas vert horiz height width where
   PowerIdentity ::
      (Layout.Packing pack) =>
      PowerQuadratic pack Unit Empty Empty sh
   PowerDiagonal ::
      (Layout.Packing pack) =>
      Power pack property Empty Empty meas vert horiz height width
   PowerUpperTriangular ::
      (Layout.Packing pack, TriDiag diag) =>
      PowerQuadratic pack diag Empty Filled sh
   PowerLowerTriangular ::
      (Layout.Packing pack, TriDiag diag) =>
      PowerQuadratic pack diag Filled Empty sh
   PowerSymmetric ::
      (Layout.Packing pack) =>
      PowerQuadratic pack Symmetric Filled Filled sh
   PowerHermitian ::
      (Layout.Packing pack, TBool.C neg, TBool.C zero, TBool.C pos) =>
      PowerQuadratic pack (Hermitian neg zero pos) Filled Filled sh
   PowerFull ::
      Power Unpacked property lower upper meas vert horiz height width

powerSingleton ::
   (Layout.Packing pack, Property property,
    PowerStrip lower, PowerStrip upper,
    Extent.Measure meas, Extent.C vert, Extent.C horiz) =>
   Omni pack property lower upper meas vert horiz height width ->
   Power pack property lower upper meas vert horiz height width
powerSingleton shape =
   case packTag shape of
      Layout.Packed ->
         case (shape, powerStrips shape) of
            (UpperTriangular _, _) -> PowerUpperTriangular
            (LowerTriangular _, _) -> PowerLowerTriangular
            (Symmetric _, _) -> PowerSymmetric
            (Hermitian _, _) -> PowerHermitian
            (Banded _, (Empty, Empty)) -> PowerDiagonal
            (BandedHermitian _, (Empty, Empty)) -> PowerDiagonal
            (UnitBandedTriangular _, (Empty, Empty)) -> PowerIdentity
      Layout.Unpacked ->
         case (extent shape, property shape, powerStrips shape) of
            (Extent.Square _, PropUnit, (Empty, Empty)) ->
               PowerIdentity
            (Extent.Square _, _, (Empty, Empty)) ->
               PowerDiagonal
            (Extent.Square _, PropArbitrary, (Empty, Filled)) ->
               PowerUpperTriangular
            (Extent.Square _, PropArbitrary, (Filled, Empty)) ->
               PowerLowerTriangular
            (Extent.Square _, PropUnit, (Empty, Filled)) ->
               PowerUpperTriangular
            (Extent.Square _, PropUnit, (Filled, Empty)) ->
               PowerLowerTriangular
            (Extent.Square _, PropSymmetric, (Filled, Filled)) ->
               PowerSymmetric
            (Extent.Square _, PropHermitian, (Filled, Filled)) ->
               PowerHermitian
            _ -> PowerFull


class
   (Unary.Natural sub, Unary.Natural super) =>
      BandedTriangular sub super where
   switchBandedTriangular ::
      f Unary.Zero Unary.Zero ->
      (forall offDiag. Unary.Natural offDiag =>
         f Unary.Zero (Unary.Succ offDiag)) ->
      (forall offDiag. Unary.Natural offDiag =>
         f (Unary.Succ offDiag) Unary.Zero) ->
      f sub super
instance BandedTriangular Unary.Zero Unary.Zero where
   switchBandedTriangular f _ _ = f
instance (Unary.Natural super) =>
      BandedTriangular Unary.Zero (Unary.Succ super) where
   switchBandedTriangular _ f _ = f
instance (Unary.Natural sub) =>
      BandedTriangular (Unary.Succ sub) Unary.Zero where
   switchBandedTriangular _ _ f = f

data BandedTriangularSingleton sub super where
   BandedDiagonal :: BandedTriangularSingleton Unary.Zero Unary.Zero
   BandedUpper ::
      Unary.Natural offDiag =>
         BandedTriangularSingleton Unary.Zero (Unary.Succ offDiag)
   BandedLower ::
      Unary.Natural offDiag =>
         BandedTriangularSingleton (Unary.Succ offDiag) Unary.Zero

bandedTriangularSingleton ::
   (BandedTriangular sub super) =>
   Layout.Banded sub super meas vert horiz height width ->
   BandedTriangularSingleton sub super
bandedTriangularSingleton _ =
   switchBandedTriangular BandedDiagonal BandedUpper BandedLower


data Omni pack property lower upper meas vert horiz height width where
   Full ::
      (Property property, Strip lower, Strip upper) =>
      Layout.Full meas vert horiz height width ->
      Omni Unpacked property lower upper meas vert horiz height width

   UpperTriangular ::
      (TriDiag diag) =>
      Layout.UpperTriangular size ->
      Omni Packed diag Empty Filled Shape Small Small size size
   LowerTriangular ::
      (TriDiag diag) =>
      Layout.LowerTriangular size ->
      Omni Packed diag Filled Empty Shape Small Small size size

   Symmetric ::
      Layout.Symmetric size ->
      Omni Packed Symmetric Filled Filled Shape Small Small size size
   Hermitian ::
      (TBool.C neg, TBool.C zero, TBool.C pos) =>
      Layout.Hermitian size ->
      Omni Packed (Hermitian neg zero pos)
         Filled Filled Shape Small Small size size

   Banded ::
      (Unary.Natural sub, Unary.Natural super) =>
      Layout.Banded sub super meas vert horiz height width ->
      Omni Packed Arbitrary
         (Bands sub) (Bands super) meas vert horiz height width

   UnitBandedTriangular ::
      (BandedTriangular sub super, BandedTriangular super sub) =>
      Layout.BandedSquare sub super size ->
      Omni Packed Unit (Bands sub) (Bands super) Shape Small Small size size

   BandedHermitian ::
      (TBool.C neg, TBool.C zero, TBool.C pos, Unary.Natural offDiag) =>
      Layout.BandedHermitian offDiag size ->
      Omni Packed (Hermitian neg zero pos)
         (Bands offDiag) (Bands offDiag) Shape Small Small size size

deriving instance
   (Extent.Measure meas, Extent.C vert, Extent.C horiz,
    Eq height, Eq width) =>
   Eq (Omni pack property lower upper meas vert horiz height width)

deriving instance
   (Extent.Measure meas, Extent.C vert, Extent.C horiz,
    Show height, Show width) =>
   Show (Omni pack property lower upper meas vert horiz height width)

instance
   (Extent.Measure meas, Extent.C vert, Extent.C horiz,
    DeepSeq.NFData height, DeepSeq.NFData width) =>
   DeepSeq.NFData
         (Omni pack property lower upper meas vert horiz height width) where
      rnf omni =
         case omni of
            Full shape -> DeepSeq.rnf shape
            UpperTriangular shape -> DeepSeq.rnf shape
            LowerTriangular shape -> DeepSeq.rnf shape
            Symmetric shape -> DeepSeq.rnf shape
            Hermitian shape -> DeepSeq.rnf shape
            Banded shape -> DeepSeq.rnf shape
            UnitBandedTriangular shape -> DeepSeq.rnf shape
            BandedHermitian shape -> DeepSeq.rnf shape


{- |
Construct a shape from order, dimensions and type information.
-}
class
   (Property property, Strip lower, Strip upper,
    ExtentS.Measured meas vert, ExtentS.Measured meas horiz) =>
      Cons pack property lower upper meas vert horiz where
   cons ::
      (Shape.C height, Shape.C width) =>
      (ExtentS.MeasureTarget meas height ~ ExtentS.MeasureTarget meas width) =>
      Layout.Order -> ExtentS.Dimension meas height width ->
      Omni pack property lower upper meas vert horiz height width

instance
   (Strip lower, Strip upper,
    ExtentS.Measured meas vert, ExtentS.Measured meas horiz) =>
      Cons Unpacked Arbitrary lower upper meas vert horiz where
   cons order_ = Full . Layout.Full order_ . ExtentS.consChecked

instance
   (TriDiag diag, Shape ~ meas, Small ~ vert, Small ~ horiz) =>
      Cons Packed diag Empty Filled meas vert horiz where
   cons order_ = UpperTriangular . Layout.upperTriangular  order_

instance
   (TriDiag diag, Shape ~ meas, Small ~ vert, Small ~ horiz) =>
      Cons Packed diag Filled Empty meas vert horiz where
   cons order_ = LowerTriangular . Layout.lowerTriangular order_

instance
   (Shape ~ meas, Small ~ vert, Small ~ horiz) =>
      Cons Packed Symmetric Filled Filled meas vert horiz where
   cons order_ = Symmetric . Layout.symmetric order_

instance
   (TBool.C neg, TBool.C zero, TBool.C pos,
    Shape ~ meas, Small ~ vert, Small ~ horiz) =>
      Cons Packed (Hermitian neg zero pos) Filled Filled meas vert horiz where
   cons order_ = Hermitian . Layout.hermitian order_

instance
   (ExtentS.Measured meas vert, ExtentS.Measured meas horiz,
    Unary.Natural sub, Unary.Natural super) =>
      Cons Packed Arbitrary (Bands sub) (Bands super) meas vert horiz where
   cons order_ =
      Banded . Layout.Banded (Proxy,Proxy) order_ . ExtentS.consChecked

instance
   (BandedTriangular sub super, BandedTriangular super sub,
    Shape ~ meas, Small ~ vert, Small ~ horiz) =>
      Cons Packed Unit (Bands sub) (Bands super) meas vert horiz where
   cons order_ =
      UnitBandedTriangular . Layout.Banded (Proxy,Proxy) order_ .
      Extent.square

instance
   (TBool.C neg, TBool.C zero, TBool.C pos, Unary.Natural sub, sub ~ super,
    Shape ~ meas, Small ~ vert, Small ~ horiz) =>
      Cons
         Packed (Hermitian neg zero pos) (Bands sub) (Bands super)
         meas vert horiz where
   cons order_ = BandedHermitian . Layout.BandedHermitian Proxy order_



class Quadratic pack property lower upper where
   quadratic ::
      (Shape.C sh) =>
      Layout.Order -> sh ->
      Omni pack property lower upper Shape Small Small sh sh

instance
   (Strip lower, Strip upper) =>
      Quadratic Unpacked Arbitrary lower upper where
   quadratic = cons

instance (TriDiag diag) => Quadratic Packed diag Empty Filled where
   quadratic = cons

instance (TriDiag diag) => Quadratic Packed diag Filled Empty where
   quadratic = cons

instance Quadratic Packed Symmetric Filled Filled where
   quadratic = cons

instance
   (TBool.C neg, TBool.C zero, TBool.C pos) =>
      Quadratic Packed (Hermitian neg zero pos) Filled Filled where
   quadratic = cons

instance
   (Unary.Natural sub, Unary.Natural super) =>
      Quadratic Packed Arbitrary (Bands sub) (Bands super) where
   quadratic = cons

instance
   (BandedTriangular sub super, BandedTriangular super sub) =>
      Quadratic Packed Unit (Bands sub) (Bands super) where
   quadratic = cons

instance
   (TBool.C neg, TBool.C zero, TBool.C pos, sub ~ super, Unary.Natural super) =>
      Quadratic Packed (Hermitian neg zero pos) (Bands sub) (Bands super) where
   quadratic = cons



class FromPlain pack property lower upper meas vert horiz height width where
   type Plain pack property lower upper meas vert horiz height width
   fromPlain ::
      Plain pack property lower upper meas vert horiz height width ->
      Omni pack property lower upper meas vert horiz height width

class
   FromPlain pack property lower upper meas vert horiz height width =>
      ToPlain pack property lower upper meas vert horiz height width where
   toPlain ::
      Omni pack property lower upper meas vert horiz height width ->
      Plain pack property lower upper meas vert horiz height width


instance
   FromPlain Unpacked Arbitrary Filled Filled meas vert horiz height width
      where
   type Plain Unpacked Arbitrary Filled Filled meas vert horiz height width =
            Layout.Full meas vert horiz height width
   fromPlain = Full

instance
   ToPlain Unpacked Arbitrary Filled Filled meas vert horiz height width
      where
   toPlain (Full shape) = shape


instance
   (Layout.Packing pack, TriDiag diag, height ~ width) =>
      FromPlain pack diag Empty Filled Shape Small Small height width where
   type Plain pack diag Empty Filled Shape Small Small height width =
            Layout.UpperTriangularP pack height
   fromPlain = fromMosaic UpperTriangular

instance
   (Layout.Packing pack, TriDiag diag, height ~ width) =>
      ToPlain pack diag Empty Filled Shape Small Small height width where
   toPlain (UpperTriangular shape) = shape
   toPlain (Full shape) = Layout.mosaicFromSquare shape


instance
   (Layout.Packing pack, TriDiag diag, height ~ width) =>
      FromPlain pack diag Filled Empty Shape Small Small height width where
   type Plain pack diag Filled Empty Shape Small Small height width =
            Layout.LowerTriangularP pack height
   fromPlain = fromMosaic LowerTriangular

instance
   (Layout.Packing pack, TriDiag diag, height ~ width) =>
      ToPlain pack diag Filled Empty Shape Small Small height width where
   toPlain (LowerTriangular shape) = shape
   toPlain (Full shape) = Layout.mosaicFromSquare shape


instance
   (Layout.Packing pack, height ~ width) =>
      FromPlain pack Symmetric Filled Filled Shape Small Small height width
         where
   type Plain pack Symmetric Filled Filled Shape Small Small height width =
            Layout.SymmetricP pack height
   fromPlain = fromMosaic Symmetric

instance
   (Layout.Packing pack, height ~ width) =>
      ToPlain pack Symmetric Filled Filled Shape Small Small height width where
   toPlain (Symmetric shape) = shape
   toPlain (Full shape) = Layout.mosaicFromSquare shape


instance
   (Layout.Packing pack,
    TBool.C neg, TBool.C zero, TBool.C pos, height ~ width) =>
      FromPlain pack (Hermitian neg zero pos) Filled Filled
            Shape Small Small height width
         where
   type Plain pack (Hermitian neg zero pos) Filled Filled
            Shape Small Small height width =
         Layout.HermitianP pack height
   fromPlain = fromMosaic Hermitian

instance
   (Layout.Packing pack,
    TBool.C neg, TBool.C zero, TBool.C pos, height ~ width) =>
      ToPlain pack (Hermitian neg zero pos) Filled Filled
            Shape Small Small height width
         where
   toPlain (Hermitian shape) = shape
   toPlain (Full shape) = Layout.mosaicFromSquare shape

fromMosaic ::
   (Layout.Packing pack, Property property, Strip lower, Strip upper) =>
   (Layout.Mosaic Packed mirror uplo sh ->
    Omni Packed property lower upper Shape Small Small sh sh) ->
   Layout.Mosaic pack mirror uplo sh ->
   Omni pack property lower upper Shape Small Small sh sh
fromMosaic packedShape shape = withPacking $ \pack ->
   case pack of
      Layout.Packed -> packedShape shape
      Layout.Unpacked -> Full $ Layout.squareFromMosaic shape

withPacking ::
   (Layout.Packing pack, Property property, Strip lower, Strip upper) =>
   (Layout.PackingSingleton pack ->
    Omni pack property lower upper meas vert horiz height width) ->
   Omni pack property lower upper meas vert horiz height width
withPacking f = f Layout.autoPacking


instance
   (Unary.Natural sub, Unary.Natural super) =>
   FromPlain Packed Arbitrary
      (Bands sub) (Bands super) meas vert horiz height width
         where
   type Plain Packed Arbitrary
            (Bands sub) (Bands super) meas vert horiz height width =
         Layout.Banded sub super meas vert horiz height width
   fromPlain = Banded

instance
   (Unary.Natural sub, Unary.Natural super) =>
   ToPlain Packed Arbitrary
      (Bands sub) (Bands super) meas vert horiz height width
         where
   toPlain (Banded shape) = shape


instance
   (BandedTriangular sub super, BandedTriangular super sub, height ~ width) =>
   FromPlain Packed Unit
      (Bands sub) (Bands super) Shape Small Small height width
         where
   type Plain Packed Unit
            (Bands sub) (Bands super) Shape Small Small height width =
            Layout.BandedSquare sub super height
   fromPlain = UnitBandedTriangular

instance
   (BandedTriangular sub super, BandedTriangular super sub, height ~ width) =>
   ToPlain Packed Unit (Bands sub) (Bands super) Shape Small Small height width
      where
   toPlain (UnitBandedTriangular shape) = shape


instance
   (Unary.Natural sub, sub ~ super, height ~ width,
    TBool.C neg, TBool.C zero, TBool.C pos) =>
      FromPlain Packed (Hermitian neg zero pos) (Bands sub) (Bands super)
         Shape Small Small height width where
   type
      Plain
         Packed (Hermitian neg zero pos) (Bands sub) (Bands super)
         Shape Small Small height width =
      Layout.BandedHermitian sub height
   fromPlain = BandedHermitian

instance
   (Unary.Natural sub, sub ~ super, height ~ width,
    TBool.C neg, TBool.C zero, TBool.C pos) =>
      ToPlain Packed (Hermitian neg zero pos) (Bands sub) (Bands super)
         Shape Small Small height width where
   toPlain (BandedHermitian shape) = shape


fromFull ::
   (Property property, Strip lower, Strip upper) =>
   Layout.Full meas vert horiz height width ->
   Omni Unpacked property lower upper meas vert horiz height width
fromFull = Full

toFull ::
   (Property property, Strip lower, Strip upper) =>
   Omni Unpacked property lower upper meas vert horiz height width ->
   Layout.Full meas vert horiz height width
toFull (Full shape) = shape


newtype FromDiagonal size diag =
   FromDiagonal {
      getFromDiagonal ::
         Layout.BandedSquare U0 U0 size ->
         Omni Packed diag Empty Empty Shape Small Small size size
   }

uncheckedDiagonal ::
   (Layout.Packing pack, TriDiag diag) =>
   Layout.Order -> size ->
   Omni pack diag Empty Empty Shape Small Small size size
uncheckedDiagonal order_ size = withPacking $ \pack ->
   case pack of
      Layout.Packed ->
         getFromDiagonal
            (switchTriDiag
               (FromDiagonal UnitBandedTriangular)
               (FromDiagonal Banded))
            (Layout.Banded (Proxy,Proxy) order_ (Extent.square size))
      Layout.Unpacked ->
         Full (Layout.Full order_ (Extent.square size))

_fromDiagonal ::
   (TriDiag diag) =>
   Layout.Diagonal size ->
   Omni Packed diag Empty Empty Shape Small Small size size
_fromDiagonal (Layout.Banded _offDiag order_ size) =
   uncheckedDiagonal order_ (Extent.squareSize size)

_toDiagonal ::
   (TriDiag diag) =>
   Omni Packed diag Empty Empty Shape Small Small size size ->
   Layout.Diagonal size
_toDiagonal omni =
   case omni of
      Banded sh -> sh
      UnitBandedTriangular sh -> sh
      BandedHermitian (Layout.BandedHermitian k order_ size) ->
         Layout.Banded (k,k) order_ (Extent.square size)


_fromUpperTriangular ::
   (Layout.Packing pack) =>
   (TriDiag diag) =>
   Layout.UpperTriangularP pack size ->
   Omni pack diag Empty Filled Shape Small Small size size
_fromUpperTriangular = fromMosaic UpperTriangular

_toUpperTriangular ::
   (TriDiag diag) =>
   Omni pack diag Empty Filled Shape Small Small size size ->
   Layout.UpperTriangularP pack size
_toUpperTriangular (UpperTriangular shape) = shape
_toUpperTriangular (Full shape) = Layout.mosaicFromSquare shape


_fromLowerTriangular ::
   (Layout.Packing pack) =>
   (TriDiag diag) =>
   Layout.LowerTriangularP pack size ->
   Omni pack diag Filled Empty Shape Small Small size size
_fromLowerTriangular = fromMosaic LowerTriangular

_toLowerTriangular ::
   (TriDiag diag) =>
   Omni pack diag Filled Empty Shape Small Small size size ->
   Layout.LowerTriangularP pack size
_toLowerTriangular (LowerTriangular shape) = shape
_toLowerTriangular (Full shape) = Layout.mosaicFromSquare shape


_fromSymmetric ::
   (Layout.Packing pack) =>
   Layout.SymmetricP pack size ->
   Omni pack Symmetric Filled Filled Shape Small Small size size
_fromSymmetric = fromMosaic Symmetric

_toSymmetric ::
   Omni pack Symmetric Filled Filled Shape Small Small size size ->
   Layout.SymmetricP pack size
_toSymmetric (Symmetric shape) = shape
_toSymmetric (Full shape) = Layout.mosaicFromSquare shape


_fromHermitian ::
   (Layout.Packing pack) =>
   (TBool.C neg, TBool.C zero, TBool.C pos) =>
   Layout.HermitianP pack size ->
   Omni pack (Hermitian neg zero pos) Filled Filled Shape Small Small size size
_fromHermitian = fromMosaic Hermitian

_toHermitian ::
   (TBool.C neg, TBool.C zero, TBool.C pos) =>
   Omni pack (Hermitian neg zero pos)
      Filled Filled Shape Small Small size size ->
   Layout.HermitianP pack size
_toHermitian (Hermitian shape) = shape
_toHermitian (Full shape) = Layout.mosaicFromSquare shape


_fromBanded ::
   (Unary.Natural sub, Unary.Natural super) =>
   Layout.Banded sub super meas vert horiz height width ->
   Omni Packed Arbitrary (Bands sub) (Bands super) meas vert horiz height width
_fromBanded = Banded

toBanded ::
   (Unary.Natural sub, Unary.Natural super) =>
   Omni Packed Arbitrary
      (Bands sub) (Bands super) meas vert horiz height width ->
   Layout.Banded sub super meas vert horiz height width
toBanded (Banded shape) = shape


_fromUnitBandedTriangular ::
   (BandedTriangular sub super, BandedTriangular super sub) =>
   Layout.Banded sub super Shape Small Small size size ->
   Omni Packed Unit (Bands sub) (Bands super) Shape Small Small size size
_fromUnitBandedTriangular = UnitBandedTriangular

_toUnitBandedTriangular ::
   (BandedTriangular sub super, BandedTriangular super sub) =>
   Omni Packed Unit (Bands sub) (Bands super) Shape Small Small size size ->
   Layout.Banded sub super Shape Small Small size size
_toUnitBandedTriangular (UnitBandedTriangular shape) = shape


_fromBandedHermitian ::
   (TBool.C neg, TBool.C zero, TBool.C pos, Unary.Natural offDiag) =>
   Layout.BandedHermitian offDiag size ->
   Omni Packed (Hermitian neg zero pos)
      (Bands offDiag) (Bands offDiag) Shape Small Small size size
_fromBandedHermitian = BandedHermitian

toBandedHermitian ::
   (TBool.C neg, TBool.C zero, TBool.C pos, Unary.Natural offDiag) =>
   Omni Packed (Hermitian neg zero pos)
      (Bands offDiag) (Bands offDiag) Shape Small Small size size ->
   Layout.BandedHermitian offDiag size
toBandedHermitian (BandedHermitian shape) = shape



height ::
   (Extent.Measure meas, Extent.C vert, Extent.C horiz) =>
   Omni pack property lower upper meas vert horiz height width -> height
height omni =
   case omni of
      Full shape -> Layout.fullHeight shape
      UpperTriangular shape -> Layout.mosaicSize shape
      LowerTriangular shape -> Layout.mosaicSize shape
      Symmetric shape -> Layout.mosaicSize shape
      Hermitian shape -> Layout.mosaicSize shape
      Banded shape -> Layout.bandedHeight shape
      UnitBandedTriangular shape -> Layout.bandedHeight shape
      BandedHermitian shape -> Layout.bandedHermitianSize shape

width ::
   (Extent.Measure meas, Extent.C vert, Extent.C horiz) =>
   Omni pack property lower upper meas vert horiz height width -> width
width omni =
   case omni of
      Full shape -> Layout.fullWidth shape
      UpperTriangular shape -> Layout.mosaicSize shape
      LowerTriangular shape -> Layout.mosaicSize shape
      Symmetric shape -> Layout.mosaicSize shape
      Hermitian shape -> Layout.mosaicSize shape
      Banded shape -> Layout.bandedWidth shape
      UnitBandedTriangular shape -> Layout.bandedWidth shape
      BandedHermitian shape -> Layout.bandedHermitianSize shape

squareSize :: Omni pack property lower upper Shape Small Small sh sh -> sh
squareSize = height

extent ::
   (Extent.Measure meas, Extent.C vert, Extent.C horiz) =>
   Omni pack property lower upper meas vert horiz height width ->
   Extent meas vert horiz height width
extent omni =
   case omni of
      Full shape -> Layout.fullExtent shape
      UpperTriangular shape -> Extent.square $ Layout.mosaicSize shape
      LowerTriangular shape -> Extent.square $ Layout.mosaicSize shape
      Symmetric shape -> Extent.square $ Layout.mosaicSize shape
      Hermitian shape -> Extent.square $ Layout.mosaicSize shape
      Banded shape -> Layout.bandedExtent shape
      UnitBandedTriangular shape -> Layout.bandedExtent shape
      BandedHermitian shape ->
         Extent.square $ Layout.bandedHermitianSize shape


mapExtentUnchecked ::
   (Extent Size vertA horizA heightA widthA ->
    Extent Size vertB horizB heightB widthB) ->
   Omni pack property lower upper Size vertA horizA heightA widthA ->
   Omni pack property lower upper Size vertB horizB heightB widthB
mapExtentUnchecked f omni =
   case omni of
      Full shape@Layout.Full{Layout.fullExtent = ext} ->
         Full shape{Layout.fullExtent = f ext}
      Banded shape@Layout.Banded{Layout.bandedExtent = ext} ->
         Banded shape{Layout.bandedExtent = f ext}

mapHeight ::
   (Shape.C heightA, Shape.C heightB, Extent.C vert, Extent.C horiz) =>
   (heightA -> heightB) ->
   Omni pack property lower upper Size vert horiz heightA width ->
   Omni pack property lower upper Size vert horiz heightB width
mapHeight f =
   mapExtentUnchecked $ Extent.mapHeight $ Layout.mapChecked "mapHeight" f

mapWidth ::
   (Shape.C widthA, Shape.C widthB, Extent.C vert, Extent.C horiz) =>
   (widthA -> widthB) ->
   Omni pack property lower upper Size vert horiz height widthA ->
   Omni pack property lower upper Size vert horiz height widthB
mapWidth f =
   mapExtentUnchecked $ Extent.mapWidth $ Layout.mapChecked "mapWidth" f

mapSquareSize ::
   (Shape.C shA, Shape.C shB) =>
   (shA -> shB) ->
   Omni pack property lower upper Shape Small Small shA shA ->
   Omni pack property lower upper Shape Small Small shB shB
mapSquareSize f omni =
   let cf = Layout.mapChecked "mapSquareSize" f in
   case omni of
      Full shape@Layout.Full{Layout.fullExtent = ext} ->
         Full shape{Layout.fullExtent = Extent.mapSquareSize cf ext}
      UpperTriangular shape@Layout.Mosaic{Layout.mosaicSize = size} ->
         UpperTriangular shape{Layout.mosaicSize = cf size}
      LowerTriangular shape@Layout.Mosaic{Layout.mosaicSize = size} ->
         LowerTriangular shape{Layout.mosaicSize = cf size}
      Symmetric shape@Layout.Mosaic{Layout.mosaicSize = size} ->
         Symmetric shape{Layout.mosaicSize = cf size}
      Hermitian shape@Layout.Mosaic{Layout.mosaicSize = size} ->
         Hermitian shape{Layout.mosaicSize = cf size}
      Banded shape@Layout.Banded{Layout.bandedExtent = ext} ->
         Banded shape{Layout.bandedExtent = Extent.mapSquareSize cf ext}
      UnitBandedTriangular shape@Layout.Banded{Layout.bandedExtent = ext} ->
         UnitBandedTriangular
            shape{Layout.bandedExtent = Extent.mapSquareSize cf ext}
      BandedHermitian
            shape@Layout.BandedHermitian{Layout.bandedHermitianSize = size} ->
         BandedHermitian shape{Layout.bandedHermitianSize = cf size}


order ::
   Omni pack property lower upper meas vert horiz height width ->
   Layout.Order
order omni =
   case omni of
      Full shape -> Layout.fullOrder shape
      UpperTriangular shape -> Layout.mosaicOrder shape
      LowerTriangular shape -> Layout.mosaicOrder shape
      Symmetric shape -> Layout.mosaicOrder shape
      Hermitian shape -> Layout.mosaicOrder shape
      Banded shape -> Layout.bandedOrder shape
      UnitBandedTriangular shape -> Layout.bandedOrder shape
      BandedHermitian shape -> Layout.bandedHermitianOrder shape

_forceOrderDiagonal ::
   Layout.Order ->
   Omni Packed property Empty Empty meas vert horiz height width ->
   Omni Packed property Empty Empty meas vert horiz height width
_forceOrderDiagonal newOrder omni =
   case omni of
      Banded sh ->
         Banded sh{Layout.bandedOrder = newOrder}
      BandedHermitian sh ->
         BandedHermitian sh{Layout.bandedHermitianOrder = newOrder}
      UnitBandedTriangular sh ->
         UnitBandedTriangular sh{Layout.bandedOrder = newOrder}


transpose ::
   (Extent.Measure meas, Extent.C vert, Extent.C horiz) =>
   Omni pack property lower upper meas vert horiz height width ->
   Omni pack property upper lower meas horiz vert width height
transpose (Full shape) =
   Full (Layout.transpose shape)
transpose (UpperTriangular shape) =
   LowerTriangular (Layout.triangularTranspose shape)
transpose (LowerTriangular shape) =
   UpperTriangular (Layout.triangularTranspose shape)
transpose (Symmetric shape) = Symmetric shape
transpose (Hermitian shape) = Hermitian shape
transpose (Banded shape) =
   Banded (Layout.bandedTranspose shape)
transpose (UnitBandedTriangular shape) =
   UnitBandedTriangular (Layout.bandedTranspose shape)
transpose (BandedHermitian shape) =
   BandedHermitian shape


instance
   (Extent.Measure meas, Extent.C vert, Extent.C horiz,
    Shape.C height, Shape.C width) =>
      Shape.C
         (Omni pack property lower upper meas vert horiz height width) where

   size omni =
      case omni of
         Full shape -> Shape.size shape
         UpperTriangular shape -> Shape.size shape
         LowerTriangular shape -> Shape.size shape
         Symmetric shape -> Shape.size shape
         Hermitian shape -> Shape.size shape
         Banded shape -> Shape.size shape
         UnitBandedTriangular shape -> Shape.size shape
         BandedHermitian shape -> Shape.size shape



type family MultipliedBands bandsA bandsB
type instance MultipliedBands Filled bandsB = Filled
type instance MultipliedBands (Bands k) Filled = Filled
type instance MultipliedBands (Bands k) (Bands l) = Bands (k Unary.:+: l)

type family MultipliedStrip contA contB
type instance MultipliedStrip Filled contB = Filled
type instance MultipliedStrip Empty contB = contB

type family MultipliedProperty propA propB
type instance MultipliedProperty Arbitrary propB = Arbitrary
type instance MultipliedProperty Symmetric propB = Arbitrary
type instance MultipliedProperty (Hermitian neg zero pos) propB = Arbitrary
type instance MultipliedProperty Unit Arbitrary = Arbitrary
type instance MultipliedProperty Unit Symmetric = Arbitrary
type instance MultipliedProperty Unit (Hermitian neg zero pos) = Arbitrary
type instance MultipliedProperty Unit Unit = Unit

type family UnitIfTriangular lower upper
type instance UnitIfTriangular Empty upper = Unit
type instance UnitIfTriangular Filled Empty = Unit
type instance UnitIfTriangular Filled Filled = Arbitrary
type instance UnitIfTriangular Filled (Bands (Unary.Succ k)) = Arbitrary
type instance UnitIfTriangular (Bands (Unary.Succ k)) Empty = Unit
type instance UnitIfTriangular (Bands (Unary.Succ k)) Filled = Arbitrary
type instance UnitIfTriangular (Bands (Unary.Succ k)) (Bands (Unary.Succ l)) =
                  Arbitrary

type family MergeUnit unit0 unit1
type instance MergeUnit Unit unit1 = unit1
type instance MergeUnit Arbitrary unit1 = Arbitrary