#!/usr/bin/env python

from aircraft import Aircraft
import util, time, math
from math import degrees, radians, sin, cos
from rotmat import Vector3, Matrix3

class HeliCopter(Aircraft):
    '''a HeliCopter simulator'''
    def __init__(self, frame='+',
                 hover_throttle=0.65,
                 terminal_velocity=40.0,
                 frame_height=0.1,
                 hover_lean=8.0,
                 yaw_zero=0.1,
                 rotor_rot_accel=radians(20),
                 mass=2.13):
        Aircraft.__init__(self)
        self.mass = mass # Kg
        self.hover_throttle = hover_throttle
        self.terminal_velocity = terminal_velocity
        self.terminal_rotation_rate = 4*radians(360.0)
        self.frame_height = frame_height
        self.last_time = time.time()
        self.roll_rate_max = radians(1400)
        self.pitch_rate_max = radians(1400)
        self.yaw_rate_max = radians(1400)
        self.rsc_setpoint = 0.8
        self.thrust_scale = (self.mass * self.gravity) / self.hover_throttle
        self.rotor_rot_accel=rotor_rot_accel

        # calculate lateral thrust ratio for tail rotor
        self.tail_thrust_scale = sin(radians(hover_lean)) * self.thrust_scale / yaw_zero

    def update(self, servos):
        # how much time has passed?
        t = time.time()
        delta_time = t - self.last_time
        self.last_time = t

        # get swash proportions (these are from 0 to 1)
        swash1 = servos[0]
        swash2 = servos[1]
        swash3 = servos[2]
        tail_rotor = servos[3]
        rsc = servos[7]

        # get total thrust from 0 to 1
        thrust = (rsc/self.rsc_setpoint)*(swash1+swash2+swash3)/3.0

        # very simplistic mapping to body euler rates
        roll_rate = swash1 - swash2
        pitch_rate = (swash1 + swash2)/2.0 - swash3
        yaw_rate = tail_rotor - 0.5

        #print(swash1, swash2, swash3, roll_rate, pitch_rate, yaw_rate, thrust, rsc)

        rsc_scale = rsc/self.rsc_setpoint

        roll_rate *= rsc_scale
        pitch_rate *= rsc_scale
        yaw_rate *= rsc_scale

        rot_accel = Vector3(roll_rate*self.roll_rate_max,
                            pitch_rate*self.pitch_rate_max,
                            yaw_rate*self.yaw_rate_max)

        # rotational air resistance
        rot_accel.x -= self.gyro.x * radians(5000.0) / self.terminal_rotation_rate
        rot_accel.y -= self.gyro.y * radians(5000.0) / self.terminal_rotation_rate
        rot_accel.z -= self.gyro.z * radians(5000.0) / self.terminal_rotation_rate

        rot_accel.z += (rsc_scale+thrust) * self.rotor_rot_accel

        # update rotational rates in body frame
        self.gyro += rot_accel * delta_time

        # update attitude
        self.dcm.rotate(self.gyro * delta_time)
        self.dcm.normalize()

        # air resistance
        air_resistance = - self.velocity * (self.gravity/self.terminal_velocity)

        thrust *= self.thrust_scale

        accel_body = Vector3(0, yaw_rate * rsc_scale * self.tail_thrust_scale, -thrust / self.mass)
        accel_earth = self.dcm * accel_body
        accel_earth += Vector3(0, 0, self.gravity)
        accel_earth += air_resistance

        # if we're on the ground, then our vertical acceleration is limited
        # to zero. This effectively adds the force of the ground on the aircraft
        if self.on_ground() and accel_earth.z > 0:
            accel_earth.z = 0

        # work out acceleration as seen by the accelerometers. It sees the kinematic
        # acceleration (ie. real movement), plus gravity
        self.accel_body = self.dcm.transposed() * (accel_earth + Vector3(0, 0, -self.gravity))

        # new velocity vector
        self.velocity += accel_earth * delta_time

        # new position vector
        old_position = self.position.copy()
        self.position += self.velocity * delta_time

        # constrain height to the ground
        if self.on_ground():
            if not self.on_ground(old_position):
                print("Hit ground at %f m/s" % (self.velocity.z))

            self.velocity = Vector3(0, 0, 0)
            # zero roll/pitch, but keep yaw
            (r, p, y) = self.dcm.to_euler()
            self.dcm.from_euler(0, 0, y)

            self.position = Vector3(self.position.x, self.position.y,
                                    -(self.ground_level + self.frame_height - self.home_altitude))

        # update lat/lon/altitude
        self.update_position()
