# -*- coding: utf-8 -*-
"""
$Id$

Copyright 2008-2009 Lode Leroy

This file is part of pyMKcam.

pyMKcam is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

pyMKcam is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with pyMKcam.  If not, see <http://www.gnu.org/licenses/>.
"""

from pymkcam.Geometry.utils import sqrt

import math
import sys

import OpenGL.GL as GL
import OpenGL.GLU as GLU
import OpenGL.GLUT as GLUT

from OpenGL.constant import Constant
GLUT_WHEEL_UP = Constant('GLUT_WHEEL_UP', 3)
GLUT_WHEEL_DOWN = Constant('GLUT_WHEEL_DOWN', 4)


_DrawCurrentSceneFunc = None
_KeyHandlerFunc = None

# Some api in the chain is translating the keystrokes to this octal string
# so instead of saying: ESCAPE = 27, we use the following.
ESCAPE = '\033'

# Number of the glut window.
window = 0

# Rotations for cube.
xrot = 110
yrot = 180
zrot = 250
scale = 0.5
xdist = 0
ydist = -1.0
zdist = -8.0

texture_num = 2
light = 1
shade_model = GL.GL_FLAT
polygon_mode = GL.GL_FILL
width = 320
height = 200

# A general OpenGL initialization function.  Sets all of the initial parameters.
def InitGL(Width, Height):
    # We call this right after our OpenGL window is created.
    global width, height
    width = Width
    height = Height

    # This Will Clear The Background Color To Black
    GL.glClearColor(0.0, 0.0, 0.0, 0.0)
    # Enables Clearing Of The Depth Buffer
    GL.glClearDepth(1.0)
    # The Type Of Depth Test To Do
    GL.glDepthFunc(GL.GL_LESS)
    # Enables Depth Testing
    GL.glEnable(GL.GL_DEPTH_TEST)
    # Enables Smooth Color Shading
#    GL.glShadeModel(GL.GL_SMOOTH)
    # Enables Flat Color Shading
#    GL.glShadeModel(GL.GL_FLAT)
    GL.glShadeModel(shade_model)

    GL.glMatrixMode(GL.GL_PROJECTION)
    # Reset The Projection Matrix
    GL.glLoadIdentity()

    # Calculate The Aspect Ratio Of The Window
    GLU.gluPerspective(60.0, float(Width)/float(Height), 0.1, 100.0)

    # Setup The Ambient Light
    GL.glLightfv(GL.GL_LIGHT0, GL.GL_AMBIENT, (0.5, 0.5, 0.5, 1.0))
    # Setup The Diffuse Light
    GL.glLightfv(GL.GL_LIGHT0, GL.GL_DIFFUSE, (1.0, 1.0, 1.0, 1.0))
    # Position The Light
    GL.glLightfv(GL.GL_LIGHT0, GL.GL_POSITION, (-10.0, 0.0, 0.0, 1.0))
    # Enable Light One
    GL.glEnable(GL.GL_LIGHT0)

    GL.glMatrixMode(GL.GL_MODELVIEW)
    GL.glMaterial(GL.GL_FRONT_AND_BACK, GL.GL_SPECULAR, (0.1, 0.1, 0.1, 1.0))
#    GL.glMaterial(GL.GL_FRONT_AND_BACK, GL.GL_SHININESS, (0.5))

    GL.glPolygonMode(GL.GL_FRONT_AND_BACK, polygon_mode)

def ReSizeGLScene(Width, Height):
    # Prevent A Divide By Zero If The Window Is Too Small
    if Height == 0:
        Height = 1

    global width, height
    width = Width
    height = Height

    # Reset The Current Viewport And Perspective Transformation
    GL.glViewport(0, 0, Width, Height)
    GL.glMatrixMode(GL.GL_PROJECTION)
    GL.glLoadIdentity()
    GLU.gluPerspective(60.0, float(Width)/float(Height), 0.1, 100.0)
    GL.glMatrixMode(GL.GL_MODELVIEW)

# The main drawing function.
def DrawGLScene():
    global xrot, yrot, zrot, scale, xdist, ydist, zdist, light

    # Clear The Screen And The Depth Buffer
    GL.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT)
    GL.glLoadIdentity()					# Reset The View
    GL.glTranslatef(xdist, ydist, zdist)			# Move Into The Screen

    GL.glRotatef(xrot, 1.0, 0.0, 0.0)			# Rotate The Cube On It's X Axis
    GL.glRotatef(yrot, 0.0, 1.0, 0.0)			# Rotate The Cube On It's Y Axis
    GL.glRotatef(zrot, 0.0, 0.0, 1.0)			# Rotate The Cube On It's Z Axis
    GL.glScalef(scale, scale, scale)
    if light:
        GL.glEnable(GL.GL_LIGHTING)
    else:
        GL.glDisable(GL.GL_LIGHTING)

    if _DrawCurrentSceneFunc:
        _DrawCurrentSceneFunc()

    # Since this is double buffered, swap the buffers to display what just got
    # drawn.
    GLUT.glutSwapBuffers()

# The function called whenever a key is pressed
def keyPressed(key, x, y):
    global light, polygon_mode, shade_model
    global xrot, yrot, zrot
    global _KeyHandlerFunc

    key = key.upper()
    if (key == ESCAPE) or (key == 'Q'):
        # If escape is pressed, kill everything.
        sys.exit()
    elif key == 'S':
        light = not light
    elif key == '=':
        print "rot=<%g,%g,%g>" % (xrot, yrot, zrot)
    elif key == 'I':
        xrot = 110
        yrot = 180
        zrot = 250
    elif key == 'T': # top
        xrot = 0
        yrot = 0
        zrot = 0
    elif key == 'F': # front
        xrot = -90
        yrot = 0
        zrot = 0
    elif key == 'R': # right
        xrot = -90
        yrot = 0
        zrot = -90
    elif key == 'L': # left
        xrot = -90
        yrot = 0
        zrot = +90
    elif key == 'M':
        if shade_model == GL.GL_SMOOTH:
            shade_model = GL.GL_FLAT
        else:
            shade_model = GL.GL_SMOOTH
        GL.glShadeModel(shade_model)
    elif key == 'P':
        if polygon_mode == GL.GL_FILL:
            polygon_mode = GL.GL_LINE
        else:
            polygon_mode = GL.GL_FILL
        GL.glPolygonMode(GL.GL_FRONT_AND_BACK, polygon_mode)
    elif _KeyHandlerFunc:
        _KeyHandlerFunc(key, x, y)

class mouseState(object):
    button = None
    state = None
    x = 0
    y = 0

def mousePressed(button, state, x, y):
    global xrot, yrot, zrot, xdist, ydist, zdist, scale
    if button == GLUT_WHEEL_DOWN:
        scale *= 1.1
    elif button == GLUT_WHEEL_UP:
        scale /= 1.1

    mouseState.button = button
    mouseState.state = state
    mouseState.x = float(x)
    mouseState.y = float(y)

def mouseMoved(x, y):
    global xrot, yrot, zrot, xdist, ydist, zdist, scale
    global width, height
    x = float(x)
    y = float(y)
    a1 = math.atan2(mouseState.y-height/2.0, mouseState.x-width/2.0)
    r1 = sqrt((mouseState.y - height / 2.0) ** 2 \
            + (mouseState.x - width / 2.0) ** 2)
    a2 = math.atan2(y-height/2.0, x-width/2.0)
    r2 = sqrt((y - height / 2.0) ** 2 + (x - width / 2.0) ** 2)
    if (mouseState.button == GLUT.GLUT_LEFT_BUTTON) \
            or (mouseState.button == GLUT.GLUT_RIGHT_BUTTON):
        a3 = math.acos(mouseState.x/width-0.5)
        a4 = math.acos(x/width-0.5)
        zrot = zrot - (a4-a3)*180/math.pi*2
    if mouseState.button == GLUT.GLUT_RIGHT_BUTTON:
        a3 = math.acos(mouseState.y/height-0.5)
        a4 = math.acos(y/height-0.5)
        if x > width / 2.0:
            yrot = yrot + (a4-a3)*180/math.pi*2
        else:
            yrot = yrot - (a4-a3)*180/math.pi*2
    if mouseState.button == GLUT.GLUT_LEFT_BUTTON:
        a3 = math.acos(mouseState.y/width-0.5)
        a4 = math.acos(y/width-0.5)
        xrot = xrot - (a4-a3)*180/math.pi*2
    mouseState.x = x
    mouseState.y = y

def Visualization(title, drawScene=DrawGLScene, width=320, height=200,
        handleKey=None):
    global window, _DrawCurrentSceneFunc, _KeyHandlerFunc
    GLUT.glutInit(sys.argv)

    _DrawCurrentSceneFunc = drawScene

    if handleKey:
        _KeyHandlerFunc = handleKey

    # Select type of Display mode:
    #  Double buffer
    #  RGBA color
    # Alpha components supported
    # Depth buffer
    GLUT.glutInitDisplayMode(GLUT.GLUT_RGBA | GLUT.GLUT_DOUBLE \
            | GLUT.GLUT_DEPTH)

    # get a 640 x 480 window
    GLUT.glutInitWindowSize(640, 480)

    # the window starts at the upper left corner of the screen
    GLUT.glutInitWindowPosition(0, 0)

    # Okay, like the C version we retain the window id to use when closing, but
    # for those of you new to Python (like myself), remember this assignment
    # would make the variable local and not global if it weren't for the global
    # declaration at the start of main.
    window = GLUT.glutCreateWindow(title)

    # Register the drawing function with glut, BUT in Python land, at least
    # using PyOpenGL, we need to set the function pointer and invoke a function
    # to actually register the callback, otherwise it would be very much like
    # the C version of the code.
    GLUT.glutDisplayFunc(DrawGLScene)

    # Uncomment this line to get full screen.
    # GLUT.glutFullScreen()

    # When we are doing nothing, redraw the scene.
    GLUT.glutIdleFunc(DrawGLScene)

    # Register the function called when our window is resized.
    GLUT.glutReshapeFunc(ReSizeGLScene)

    # Register the function called when the keyboard is pressed.
    GLUT.glutKeyboardFunc(keyPressed)

    # Register the function called when the mouse is pressed.
    GLUT.glutMouseFunc(mousePressed)

    # Register the function called when the mouse is pressed.
    GLUT.glutMotionFunc(mouseMoved)

    # Initialize our window.
    InitGL(640, 480)

    # Start Event Processing Engine
    GLUT.glutMainLoop()


test_model = None
test_cutter = None
test_pathlist = None

def DrawTestScene():
    global test_model, test_cutter, test_pathlist
    if test_model:
        GL.glColor4f(1, 0.5, 0.5, 0.1)
        test_model.to_OpenGL()
    if test_cutter:
        GL.glColor3f(0.5, 0.5, 0.5)
        test_cutter.to_OpenGL()
    if test_pathlist:
        for path in test_pathlist:
            GL.glColor3f(0.5, 0.5, 1)
            GL.glBegin(GL.GL_LINE_STRIP)
            for point in path.points:
                GL.glVertex3f(point.x, point.y, point.z)
#                GL.glVertex3f(point.x, point.y, point.z+1)
            GL.glEnd()

def ShowTestScene(model=None, cutter=None, pathlist=None):
    global test_model, test_cutter, test_pathlist
    test_model = model
    test_cutter = cutter
    test_pathlist = pathlist
    Visualization("TestScene", DrawTestScene)
