------------------------------------------------------------
-- |
-- Module      :  Data.Tuple.Ops.Internal
-- Description :  various operations on n-ary tuples via GHC.Generics
-- Copyright   :  (c) 2018 Jiasen Wu
-- License     :  BSD-style (see the file LICENSE)
-- Maintainer  :  Jiasen Wu <jiasenwu@hotmail.com>
-- Stability   :  experimental
-- Portability :  portable
--
--
-- This module defins operations to manipulate the generic 
-- representation of tuple.
------------------------------------------------------------
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE ScopedTypeVariables #-}

module Data.Tuple.Ops.Internal where

import qualified GHC.Generics as G
import GHC.Generics (Generic(..), (:*:)(..), (:+:)(..), Rec0, C1, D1, S1, M1(..), U1, K1(..))
import GHC.TypeLits
import Data.Proxy
import qualified Prelude as P 
import Prelude (Maybe(..), Int, Word, Char, Float, Double, Bool(..), ($))

-- | a sentinial datatype that represents a empty product
data End a = End

-- | Representation of tuple are shaped in a balanced tree. 
-- 'L' transforms the tree into a line, for further manipulation.
class Linearize (t :: * -> *) (b :: * -> *) where
  type L t b :: * -> *
  linearize :: t x -> b x -> L t b x

-- | base case 1. cons field with end
instance Linearize (S1 MetaS (Rec0 t)) End where
    type L (S1 MetaS (Rec0 t)) End = (S1 MetaS (Rec0 t))
    linearize a End = a

-- note that it is not possible to combine base case 2 and 3, which results in
-- a ambiguous instantiation with base case 1
--
-- | base case 2. cons field1 with field2 
instance Linearize (S1 MetaS (Rec0 t)) (S1 MetaS (Rec0 b)) where
    type L (S1 MetaS (Rec0 t)) (S1 MetaS (Rec0 b)) = S1 MetaS (Rec0 t) :*: S1 MetaS (Rec0 b)
    linearize a b = a :*: b

-- | base case 3. cons field1 with a product
instance Linearize (S1 MetaS (Rec0 t)) (b :*: c) where
    type L (S1 MetaS (Rec0 t)) (b :*: c) = S1 MetaS (Rec0 t) :*: b :*: c
    linearize a b = a :*: b

-- | inductive case. preppend a product with what ever
instance (Linearize v b, Linearize u (L v b)) => Linearize (u :*: v) b where
    type L (u :*: v) b = L u (L v b)
    linearize (a :*: b) c = linearize a (linearize b c)

-- | calculate the number of fields of a product
-- note: undefined on non-product rep
type family Length a :: Nat where
    Length (S1 MetaS (Rec0 t)) = 1
    Length (a :*: b) = Length a + Length b
-- | calculate the number of fields of a product
length :: a x -> Proxy (Length a)
length _ = Proxy

-- | calculate the half
type family Half (a :: Nat) :: Nat where
    Half 1 = 0
    Half 2 = 1
    Half n = Half (n - 2) + 1
-- | calculate the half
half :: KnownNat n => Proxy n -> Proxy (Half n)
half _ = Proxy

-- | Positive natural number in type level
-- We rely on the SNat to define Take and Drop
data SNat = One | Succ SNat
-- | transform the GHC's typelit into SNat
type family ToSNat (a :: Nat) :: SNat where
    ToSNat 1 = One
    ToSNat n = Succ (ToSNat (n - 1))
-- | transform the GHC's typelit into SNat
tosnat :: KnownNat n => Proxy n -> Proxy (ToSNat n)
tosnat _ = Proxy

-- | take the first n elements from a product
class Take (n :: SNat) (a :: * -> *) where
    type T n a :: * -> *
    take :: Proxy n -> a x -> T n a x

-- | base case 1. take one out of singleton
instance Take One (S1 MetaS (Rec0 t)) where
    type T One (S1 MetaS (Rec0 t)) = S1 MetaS (Rec0 t)
    take _ a = a

-- | base case 2. take one out of a product
instance Take One (a :*: b) where
    type T One (a :*: b) = a
    take _ (a :*: _) = a

-- | inductive case. take (n+1) elements
instance Take n b => Take (Succ n) (a :*: b) where
    type T (Succ n) (a :*: b) = a :*: T n b
    take (_ :: Proxy (Succ n)) (a :*: b) = a :*: take (Proxy :: Proxy n) b

-- | drop the first n elements from a product
class Drop (n :: SNat) (a :: * -> *) where
    type D n a :: * -> *
    drop :: Proxy n -> a x -> D n a x

-- | base case 1. drop one from product
instance Drop One (a :*: b) where
    type D One (a :*: b) = b
    drop _ (_ :*: b) = b

-- | inductive case. drop (n+1) elements
instance Drop n b => Drop (Succ n) (a :*: b) where
    type D (Succ n) (a :*: b) = D n b
    drop (_ :: Proxy (Succ n)) (a :*: b) = drop (Proxy :: Proxy n) b

-- | 'Normalize' converts a linear product back into a balanced tree.
class Normalize (a :: * -> *) where
    type N a :: * -> * 
    normalize :: a x -> N a x

-- | base case 1. singleton
instance Normalize (S1 MetaS (Rec0 t)) where
    type N (S1 MetaS (Rec0 t)) = S1 MetaS (Rec0 t)
    normalize a = a

-- | inductive case. product
instance (KnownNat (Length a + Length b),
          KnownNat (Half (Length a + Length b)),
          Take (ToSNat (Half (Length a + Length b))) (a :*: b),
          Drop (ToSNat (Half (Length a + Length b))) (a :*: b),
          Normalize ((T (ToSNat (Half (Length a + Length b))) (a :*: b))), 
          Normalize ((D (ToSNat (Half (Length a + Length b))) (a :*: b)))) 
    => Normalize (a :*: b) where
    type N (a :*: b) = N (T (ToSNat (Half (Length a + Length b))) (a :*: b)) :*: 
                       N (D (ToSNat (Half (Length a + Length b))) (a :*: b))
    normalize v = let n1 = length v :: Proxy (Length a + Length b)
                      n2 = tosnat $ half n1
                  in normalize (take n2 v) :*: normalize (drop n2 v)

type MetaS = 'G.MetaSel 'Nothing 'G.NoSourceUnpackedness 'G.NoSourceStrictness 'G.DecidedLazy
-- | utility type function to trim the Rec0 
type family UnRec0 t where
    UnRec0 (Rec0 t) = t
-- | utility type function to trim the S1
type family UnS1 t where
    UnS1 (S1 _ t) = t
-- | utility type function to trim the D1
type family UnD1 t where
    UnD1 (D1 _ t) = t