# encoding: utf8

import string
import time
from math import acos, cos, pi, sin, sqrt

import gtk

from glitch.gtk import GtkCamera

bindings = {
    'w':           'forward',
    'a':           'left',
    's':           'back',
    'd':           'right',
    'Up':          'forward',
    'Down':        'back',
    'Left':        'left',
    'Right':       'right',
    'F11':         'toggle-lighting',
    'F12':         'toggle-debug',
    'W':           'turn-up',
    'A':           'turn-left',
    'S':           'turn-down',
    'D':           'turn-right',
    'Shift-Left':  'turn-left',
    'Shift-Right': 'turn-right',
    'Shift-Up':    'turn-up',
    'Shift-Down':  'turn-down',
    'Page_Up':     'go-up',
    'Page_Down':   'go-down',
    }

class GtkMovingCamera(GtkCamera):
    def __init__(self, grab_mouse=False, **kw):
        GtkCamera.__init__(self, **kw)
        self.have_grab = False
        self.last_event_time = None
        self.add_events(gtk.gdk.POINTER_MOTION_MASK)
        self.connect('grab-broken-event', self.grab_broken)
        self.connect('key-press-event', self.key_pressed)

        if grab_mouse:
            self.connect('motion-notify-event', self.motion_notify)
            self.connect('visibility-notify-event', self.visibility)

    def visibility(self, _widget, event):
        if not self.have_grab:
            result = self.grab()

            if result == gtk.gdk.GRAB_SUCCESS:
                self.have_grab = True

    def grab_broken(self, _widget, event):
        self.have_grab = False

    def grab(self):
        window = self.window
        pixmap = gtk.gdk.Pixmap(window, 1, 1, 1)
        color = gtk.gdk.Color()
        blank_cursor = gtk.gdk.Cursor(pixmap, pixmap, color, color, 0, 0)
        return gtk.gdk.pointer_grab(
            window, False, gtk.gdk.POINTER_MOTION_MASK, None,
            blank_cursor, 0)

    def get_orientation(self):
        (ex, ey, ez) = self.eye
        (rx, ry, rz) = self.ref
        r1 = sqrt((ex - rx) ** 2 + (ez - rz) ** 2)
        yaw = acos((ez - rz) / r1)

        if ex - rx < 0:
            yaw *= -1

        r2 = sqrt((ex - rz) ** 2 + (ez - rz) ** 2 + (ey - ry) ** 2)
        pitch = acos((ey - ry) / r2)
        return (yaw, pitch)

    def set_orientation(self, yaw, pitch):
        (ex, ey, ez) = self.eye
        (rx, ry, rz) = self.ref
        r1 = sqrt((ex - rx) ** 2 + (ez - rz) ** 2)
        r2 = sqrt((ex - rz) ** 2 + (ey - ry) ** 2 + (ez - rz) ** 2)
        self.ref[0] = ex - r1 * sin(yaw)
        self.ref[1] = ey - r2 * cos(pitch)
        self.ref[2] = ez - r1 * cos(yaw)

    def key_pressed(self, _widget, event):
        name = gtk.gdk.keyval_name(event.keyval)

        if name not in string.uppercase and event.state & gtk.gdk.SHIFT_MASK:
            name = 'Shift-' + name

        slow = False
        if event.state & gtk.gdk.CONTROL_MASK:
            slow = True

        if name in bindings:
            action = bindings[name].replace('-', '_')
            method = getattr(self, action)
            method(slow)

    def toggle_lighting(self, *args):
        self.ctx['lighting'] = not self.ctx['lighting']
        print 'lighting = %r' % self.ctx['lighting']
        self.refresh()

    def toggle_debug(self, *args):
        self.ctx['debug'] = not self.ctx['debug']
        print 'debug = %r' % self.ctx['debug']
        self.refresh()

    def forward(self, slow):
        (yaw, pitch) = self.get_orientation()
        self.eye[0] -= (0.04 if slow else 0.4) * sin(yaw)
        self.eye[2] -= (0.04 if slow else 0.4) * cos(yaw)
        self.ref[0] -= (0.04 if slow else 0.4) * sin(yaw)
        self.ref[2] -= (0.04 if slow else 0.4) * cos(yaw)
        self.refresh()

    def back(self, slow):
        (yaw, pitch) = self.get_orientation()
        self.eye[0] += (0.04 if slow else 0.4) * sin(yaw)
        self.eye[2] += (0.04 if slow else 0.4) * cos(yaw)
        self.ref[0] += (0.04 if slow else 0.4) * sin(yaw)
        self.ref[2] += (0.04 if slow else 0.4) * cos(yaw)
        self.refresh()

    def left(self, slow):
        (yaw, pitch) = self.get_orientation()
        self.eye[0] -= (0.04 if slow else 0.4) * sin(yaw + pi / 2)
        self.eye[2] -= (0.04 if slow else 0.4) * cos(yaw + pi / 2)
        self.ref[0] -= (0.04 if slow else 0.4) * sin(yaw + pi / 2)
        self.ref[2] -= (0.04 if slow else 0.4) * cos(yaw + pi / 2)
        self.refresh()

    def right(self, slow):
        (yaw, pitch) = self.get_orientation()
        self.eye[0] += (0.04 if slow else 0.4) * sin(yaw + pi / 2)
        self.eye[2] += (0.04 if slow else 0.4) * cos(yaw + pi / 2)
        self.ref[0] += (0.04 if slow else 0.4) * sin(yaw + pi / 2)
        self.ref[2] += (0.04 if slow else 0.4) * cos(yaw + pi / 2)
        self.refresh()

    def turn_left(self, slow):
        self.turn(-(0.01 if slow else 0.1), 0)

    def turn_right(self, slow):
        self.turn((0.01 if slow else 0.1), 0)

    def turn_up(self, slow):
        self.turn(0, -(0.01 if slow else 0.1))

    def turn_down(self, slow):
        self.turn(0, (0.01 if slow else 0.1))

    def go_up(self, slow):
        self.eye[1] += (0.01 if slow else 0.1)
        self.ref[1] += (0.01 if slow else 0.1)
        self.refresh()

    def go_down(self, slow):
        self.eye[1] -= (0.01 if slow else 0.1)
        self.ref[1] -= (0.01 if slow else 0.1)
        self.refresh()

    def turn(self, d_yaw, d_pitch):
        (ex, ey, ez) = self.eye
        (yaw, pitch) = self.get_orientation()
        self.set_orientation(yaw - d_yaw, pitch - d_pitch)
        self.refresh()

    def motion_notify(self, _widget, event):
        t = time.time()

        if (self.last_event_time is not None and
                t - self.last_event_time < 0.05):
            return False

        display = gtk.gdk.display_get_default()
        screen = display.get_default_screen()
        (w, h) = (screen.get_width(), screen.get_height())

        if self.last_event_time is not None:
            d_yaw = (event.x_root - w / 2) / 100.0
            d_pitch = (event.y_root - h / 2) / 100.0

            if (d_yaw, d_pitch) != (0, 0):
                self.turn(d_yaw, d_pitch)

        display.warp_pointer(screen, w / 2, h / 2)
        self.last_event_time = t
        return True

    def make_window(self, *args, **kwargs):
        window = GtkCamera.make_window(self, *args, **kwargs)
        self.grab_add()
        return window

