{-# LANGUAGE TypeOperators, TypeFamilies, FlexibleContexts
           , TypeSynonymInstances, MultiParamTypeClasses, Rank2Types
           , FlexibleInstances, ScopedTypeVariables
           , ConstraintKinds  #-}
{-# OPTIONS_GHC -Wall -fno-warn-orphans #-}
----------------------------------------------------------------------
-- |
-- Module      :  Shady.Image
-- Copyright   :  (c) Conal Elliott 2009
-- License     :  GPLv3
--
-- Maintainer  :  conal@conal.net
-- Stability   :  experimental
--
-- Images (infinite & continuous)
----------------------------------------------------------------------

-- This variation uses Complex

module Shady.Image
  (
    Point, pointToR2, r2ToPoint
  , FilterG, Filter, samplerIm, scale2, uscale2, translate2, rotate2
  , bilerp, bilerpC
  , ImageG, Image
    -- * General regions
  , PRegion, Region
  , universeR, emptyR, eqF, neqF, intersectR, unionR, xorR, diffR, complementR
  , udisk, disk, annulus, checker
  , crop
    -- * Space-varying transformations
  , transformG, translate2Im, scale2Im, uscale2Im, rotate2Im
  , swirl -- , uswirl
  , utile, tile
  ) where

import Control.Applicative (Applicative(..),liftA2)
import Shady.Complex

import Data.VectorSpace

import Data.Boolean

import Shady.Misc
import Shady.Language.Exp
import Shady.Color
import Shady.ITransform

type Point = ComplexE R

pointToR2 :: Point -> R2E
pointToR2 (x :+ y) = vec2 x y

r2ToPoint :: R2E -> Point
r2ToPoint xy = getX xy :+ getY xy

-- | Generalized image -- continuous & infinite
type ImageG s a = Complex s -> a

-- | Continuous, infinite image
type Image a = ImageG FloatE a


-- == Point -> a

-- | Generalized filter, polymorphic over domain
type FilterG p a = Unop (p -> a)

-- | Image filter
type Filter a = FilterG Point a
                 -- Unop (Image a)


-- | Wrap up a sampler as an image
samplerIm :: Sampler2 :=> Image Color
samplerIm s = r4ToColor . texture s . pointToR2


-- -- | 2D invertible transform
-- type ITransform2 = ITransform Point

translate2X :: AdditiveGroup a => a         -> ITransform a
scale2X     :: Fractional    s => Complex s -> ITransform (Complex s)
uscale2X    :: Fractional    s => s         -> ITransform (Complex s)
rotate2X    :: (AdditiveGroup s, Eq s, Floating s) => s -> ITransform (Complex s)

translate2X = andInverse (^+^) negateV
scale2X     = andInverse (onRI2 (*)) (onRI recip)
rotate2X    = andInverse rotate2C     negate
uscale2X    = scale2X . \ a -> a :+ a

rotate2C :: (AdditiveGroup s, Eq s, Floating s) => s -> Unop (Complex s)
rotate2C theta = (cis theta *)

-- experiment

-- translate2, scale2 :: (AdditiveGroup s, Eq s, Floating s, ITrans (Complex s) a) => Complex s -> Unop a
-- uscale2,rotate2    :: (AdditiveGroup s, Eq s, Floating s, ITrans (Complex s) a) => s -> Unop a

-- type TransScalar s = (AdditiveGroup s, Eq s, Floating s)
type TransScalar s = (s ~ E R1)

translate2, scale2 :: (TransScalar s, ITrans (Complex s) a) => Complex s -> Unop a
uscale2,rotate2    :: (TransScalar s, ITrans (Complex s) a) => s -> Unop a

translate2 = (*:) . translate2X
scale2     = (*:) . scale2X
rotate2    = (*:) . rotate2X
uscale2    = (*:) . uscale2X

-- translate2 :: ITransform Point
-- (*:) :: ITransform w -> Unop a

-- (*:) . translate2 :: ITransform Point
--  :: ITransform w -> Unop a


-- | Bilinear interpolation
bilerp :: VectorSpace w =>
          w -> w -> w -> w -> (Scalar w, Scalar w) -> w
bilerp ll lr ul ur (dx,dy) =
  lerp (lerp ll lr dx) (lerp ul ur dx) dy

-- | Bilinear interpolation image
bilerpC :: (VectorSpace w, Scalar w ~ s) =>
           w -> w -> w -> w -> ImageG s w
bilerpC ll lr ul ur (dx :+ dy) = bilerp ll lr ul ur (dx,dy)


{--------------------------------------------------------------------
    Generalized regions
--------------------------------------------------------------------}

-- TODO: Move most of these definitions elsewhere, since they're not
-- specific to 2D.

-- | Region over general space
type PRegion p = p -> BoolE

-- | 2D spatial region
type Region = Image BoolE

universeR, emptyR :: Applicative f => f BoolE
universeR = pure true
emptyR    = pure false

eqF, neqF :: (IsNat n, IsScalar a, Eq a, Applicative f) =>
             f (VecE n a) -> f (VecE n a) -> f BoolE

eqF  = liftA2 (==^)
neqF = liftA2 (/=^)

-- intersectR, unionR, xorR, diffR
--   :: LiftA2 BoolE BoolE BoolE b b b => b -> b -> b
-- complementR :: LiftA1 BoolE BoolE b b => b -> b

intersectR, unionR, xorR, diffR :: Applicative f => Binop (f BoolE)
complementR                     :: Applicative f => Unop  (f BoolE)

intersectR  = liftA2 (&&*)
unionR      = liftA2 (||*)
complementR = fmap notE
xorR        = neqF

diffR r r' = r `intersectR` complementR r'


-- | Generalized unit disk/ball
udisk :: (InnerSpace p, Scalar p ~ FloatE) => PRegion p
udisk p = magnitudeSq p <=* 1

-- | Generalized disk/ball, given radius
disk :: (InnerSpace p, Scalar p ~ FloatE) => FloatE -> PRegion p
disk s = udisk . (^/ s)

-- | Generalized annulus, given outer & inner radii
annulus :: (InnerSpace p, Scalar p ~ FloatE) => FloatE -> FloatE -> PRegion p
annulus o i = disk o `diffR` disk i


-- | Checker-board
checker :: Region
checker (x :+ y) = getX c ==* getY c
  where c = frac (x <+> y) >* 0.5

-- checker (x :+ y) = big x ==* big y
--  where
--    big = (>* 0.5) . frac


{--------------------------------------------------------------------
    Some generalized transforms
--------------------------------------------------------------------}

-- | General domain-varying transformation.
transformG' :: (c -> Unop p) -> (p -> c) -> Unop (p -> a)
transformG' f imc ima p = ima (f (imc p) p)

-- transformG' :: (c -> Unop Point) -> Image c -> Filter a

-- | General domain-varying transformation.
transformG :: (c -> ITransform p) -> (p -> c) -> Unop (p -> a)
transformG f = transformG' (itBackward . f)

-- transformG :: (c -> ITransform2) -> Image c -> Filter a

-- translate2Im :: Image Point -> Filter a
-- scale2Im :: Image Point -> Filter a
-- uscale2Im :: Image FloatE -> Filter a
-- rotate2Im :: Image FloatE -> Filter a


-- | Space-varying 'translate2'
translate2Im :: AdditiveGroup p => Unop p -> Unop (p -> a)
translate2Im = transformG translate2X

-- | Space-varying 'scale2'
scale2Im :: Fractional s => Unop (Complex s) -> Unop (ImageG s a)
scale2Im = transformG scale2X

-- | Space-varying 'uscale2'
uscale2Im :: Fractional s => ImageG s s -> Unop (ImageG s a)
uscale2Im = transformG uscale2X

-- | Space-varying 'rotate2'
rotate2Im :: (AdditiveGroup s, Eq s, Floating s) => ImageG s s -> Unop (ImageG s a)
rotate2Im = transformG rotate2X


{--------------------------------------------------------------------
    Other transformations
--------------------------------------------------------------------}

-- -- | Unit swirl
-- uswirl :: Filter a
-- uswirl = rotate2Im magnitude

-- -- | Swirl transformation
-- swirl :: FloatE -> Filter a
-- swirl s = hyperUscale2 s uswirl

-- *Almost* equivalent, but differs for negative s.

-- | Swirl transformation
swirl :: (AdditiveGroup s, Eq s, Floating s) => s -> Unop (ImageG s a)
swirl s = rotate2Im ((2*pi*s*) . magnitude)

utile' :: Frac p => Unop (p -> a)
utile' = (. frac)

-- Hm!  This utile' definition repeats [0,1), not [-.5,.5).  Eep.  How can
-- I shift without loss of generality?  For instance, the current
-- definition can handle nD.

-- | Unit, rectangular tiling.
-- utile :: (AdditiveGroup s, Eq s, Frac p, ITrans (Complex s) p, ITrans (Complex s) a, Floating s) =>
--          Unop (p -> a)
-- utile :: Unop (Image a)
-- utile = translate2 (negate (0.5 :+ 0.5)) utile'

-- -- utile :: Unop (Image a)
-- utile :: forall p a s. (Fractional s, Frac p, ITrans (Complex s) a) =>
--          Unop (p -> a)
-- utile = translate2 (negate (0.5 :+ 0.5 :: Complex s)) utile'

utile :: (Frac p, AdditiveGroup p, Floating p, Eq p) =>
         Unop (Complex p -> a)
utile f = utile' (f . subtract (0.5 :+ 0.5))

-- TODO: Generalize uniform scaling to arbitrary vector spaces, scaling
-- via scalar field.

-- Rectangle tiling with given size.
-- tile :: ITrans Point a => Point -> Filter a

-- tile :: (AdditiveGroup s, Eq s, Floating s, Frac s, ITrans (Complex s) a) =>
--         Complex s -> Unop (ImageG s a)
-- tile :: ( AdditiveGroup s, Floating s, Eq s
--         , ITrans (Complex s) Point, ITrans (Complex s) a) =>
--         Complex s -> Unop (Image a)

tile :: ( TransScalar s
        , AdditiveGroup p, Floating p, Eq p
        , ITrans (Complex s) (Complex p), ITrans (Complex s) a, Frac p
        ) =>
        Complex s -> Unop (Complex p -> a)
tile s = scale2 s utile

-- tile = flip scale2 utile

-- instance ITrans (Complex s) (Complex s) where (*:) = itForward


{--------------------------------------------------------------------
    Orphans
--------------------------------------------------------------------}

-- Standard do-nothing transformation
instance ITrans Point Color where (*:) = const id
