{-# LANGUAGE ApplicativeDo     #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes       #-}
{-# LANGUAGE RecordWildCards   #-}
{- HLINT ignore -}
{-|
Copyright   : Written by David Himmelstrup
License     : Unlicense
Maintainer  : lemmih@gmail.com
Stability   : experimental
Portability : POSIX
-}
module Reanimate.Builtin.Flip
  ( FlipSprite(..)
  , flipSprite
  , flipTransition
  ) where

import           Reanimate.Animation        (Animation, duration, frameAt, setDuration)
import           Reanimate.Blender          (blender)
import           Reanimate.Ease             (fromToS, oscillateS)
import           Reanimate.Raster           (svgAsPngFile)
import           Reanimate.Scene            (Scene, Sprite, Var, fork, newSprite, newVar, scene,
                                             spriteDuration, spriteT, tweenVar, unVar)
import           Reanimate.Svg.Constructors (flipXAxis)
import           Reanimate.Transition       (Transition)

import qualified Data.Text                  as T
import           NeatInterpolation          (text)

-- | Control structure with parameters for the blender script.
data FlipSprite s = FlipSprite
  { fsSprite :: Sprite s
  , fsBend   :: Var s Double
  , fsZoom   :: Var s Double
  , fsWobble :: Var s Double
  }

-- | Project two animations on each side of a plane and flip the plane
--   upside down.
flipSprite :: Animation -> Animation -> Scene s (FlipSprite s)
flipSprite front back = do
    bend <- newVar 0
    trans <- newVar 0
    rotX <- newVar 0
    sprite <- newSprite $ do
      getBend <- unVar bend
      getTrans <- unVar trans
      getRotX <- unVar rotX
      time <- spriteT
      dur <- spriteDuration
      return $
        let rotY = fromToS 0 pi (time/dur)
            frontTexture = svgAsPngFile (frameAt time $ setDuration dur front)
            backTexture = svgAsPngFile (flipXAxis $ frameAt time $ setDuration dur back)
           -- seq'ing frontTexture and backTexture is required to avoid segfaults. :(
        in frontTexture `seq` backTexture `seq`
           blender (script frontTexture backTexture getBend getTrans getRotX rotY)
    return FlipSprite
      { fsSprite = sprite
      , fsBend = bend
      , fsZoom = trans
      , fsWobble = rotX }

flipTransitionOpts :: Double -> Double -> Double -> Transition
flipTransitionOpts bend zoom wobble a b = scene $ do
    FlipSprite{..} <- flipSprite a b
    fork $ tweenVar fsZoom dur   $ \v -> fromToS v zoom . oscillateS
    fork $ tweenVar fsBend dur   $ \v -> fromToS v bend . oscillateS
    fork $ tweenVar fsWobble dur $ \v -> fromToS v wobble . oscillateS
  where
    dur = max (duration a) (duration b)

-- | 3D flip transition.
flipTransition :: Transition
flipTransition = flipTransitionOpts bend zoom wobble
  where
    bend = 1/3
    zoom = 3
    wobble = -pi*0.10

script :: FilePath -> FilePath -> Double -> Double -> Double -> Double -> T.Text
script frontImage backImage bend transZ rotX rotY =
  let transZ_ = T.pack (show transZ)
      rotX_ = T.pack (show rotX)
      bend_ = T.pack (show bend)
      yScale_ = T.pack (show $ fromToS (9/2) 4 bend)
      frontImage_ = T.pack frontImage
      backImage_ = T.pack backImage
      rotY_ = T.pack (show rotY)
  in [text|
import os
import math

import bpy

light = bpy.data.objects['Light']
bpy.ops.object.select_all(action='DESELECT')
light.select_set(True)
bpy.ops.object.delete()


cam = bpy.data.objects['Camera']
cam.location = (0,0,22.22 + $transZ_)
cam.rotation_euler = (0, 0, 0)
bpy.ops.object.empty_add(location=(0.0, 0, 0))
focus_target = bpy.context.object
bpy.ops.object.select_all(action='DESELECT')
cam.select_set(True)
focus_target.select_set(True)
bpy.ops.object.parent_set()

focus_target.rotation_euler = ($rotX_, 0, 0)


origin = bpy.data.objects['Cube']
bpy.ops.object.select_all(action='DESELECT')
origin.select_set(True)
bpy.ops.object.delete()

x = $bend_
bpy.ops.mesh.primitive_plane_add()
plane = bpy.context.object
plane.scale = (16/2,$yScale_,1)
bpy.ops.object.shade_smooth()

bpy.context.object.active_material = bpy.data.materials['Material']
mat = bpy.context.object.active_material
mix = mat.node_tree.nodes.new('ShaderNodeMixShader')
geo = mat.node_tree.nodes.new('ShaderNodeNewGeometry')

mat.blend_method = 'HASHED'

image_node = mat.node_tree.nodes.new('ShaderNodeTexImage')
gh_node = mat.node_tree.nodes.new('ShaderNodeTexImage')
output = mat.node_tree.nodes['Material Output']

gh_mix = mat.node_tree.nodes.new('ShaderNodeMixShader')
transparent = mat.node_tree.nodes.new('ShaderNodeBsdfTransparent')

mat.node_tree.links.new(geo.outputs['Backfacing'], mix.inputs['Fac'])
mat.node_tree.links.new(mix.outputs['Shader'], output.inputs['Surface'])
mat.node_tree.links.new(image_node.outputs['Color'], mix.inputs[1])

#mat.node_tree.links.new(gh_node.outputs['Color'], mix.inputs[2])
mat.node_tree.links.new(gh_node.outputs['Color'], gh_mix.inputs[2])
mat.node_tree.links.new(gh_node.outputs['Alpha'], gh_mix.inputs['Fac'])
mat.node_tree.links.new(transparent.outputs['BSDF'], gh_mix.inputs[1])
mat.node_tree.links.new(gh_mix.outputs['Shader'], mix.inputs[2])

image_node.image = bpy.data.images.load('${frontImage_}')
image_node.interpolation = 'Closest'

gh_node.image = bpy.data.images.load('${backImage_}')
gh_node.interpolation = 'Closest'


modifier = plane.modifiers.new(name='Subsurf', type='SUBSURF')
modifier.levels = 7
modifier.render_levels = 7
modifier.subdivision_type = 'SIMPLE'

bpy.ops.object.empty_add(type='ARROWS',rotation=(math.pi/2,0,0))
empty = bpy.context.object

bendUp = plane.modifiers.new(name='Bend up', type='SIMPLE_DEFORM')
bendUp.deform_method = 'BEND'
bendUp.origin = empty
bendUp.deform_axis = 'X'
bendUp.factor = -math.pi*x

bendAround = plane.modifiers.new(name='Bend around', type='SIMPLE_DEFORM')
bendAround.deform_method = 'BEND'
bendAround.origin = empty
bendAround.deform_axis = 'Z'
bendAround.factor = -math.pi*2*x

bpy.context.view_layer.objects.active = plane
bpy.ops.object.modifier_apply(modifier='Subsurf')
bpy.ops.object.modifier_apply(modifier='Bend up')
bpy.ops.object.modifier_apply(modifier='Bend around')

bpy.ops.object.select_all(action='DESELECT')
plane.select_set(True);
bpy.ops.object.origin_clear()
bpy.ops.object.origin_set(type='GEOMETRY_ORIGIN')

plane.rotation_euler = (0, $rotY_, 0)

scn = bpy.context.scene

#scn.render.engine = 'CYCLES'
#scn.render.resolution_percentage = 10

scn.view_settings.view_transform = 'Standard'


scn.render.resolution_x = 2560
scn.render.resolution_y = 1440

scn.render.film_transparent = True

bpy.ops.render.render( write_still=True )
|]