#!/usr/bin/env python
'''
simple rover simulator
'''

from rover import Rover
import util, time, os, sys, math
import socket, struct
import select, errno

def sim_send(a):
    '''send flight information to mavproxy'''
    from math import degrees

    earth_rates = util.BodyRatesToEarthRates(a.dcm, a.gyro)
    (roll, pitch, yaw) = a.dcm.to_euler()

    buf = struct.pack('<17dI',
                      a.latitude, a.longitude, a.altitude, degrees(yaw),
                      a.velocity.x, a.velocity.y, a.velocity.z,
                      a.accelerometer.x, a.accelerometer.y, a.accelerometer.z,
                      degrees(earth_rates.x), degrees(earth_rates.y), degrees(earth_rates.z),
                      degrees(roll), degrees(pitch), degrees(yaw),
                      math.sqrt(a.velocity.x*a.velocity.x + a.velocity.y*a.velocity.y),
                      0x4c56414f)
    try:
        sim_out.send(buf)
    except socket.error as e:
        if not e.errno in [ errno.ECONNREFUSED ]:
            raise


def sim_recv(state):
    '''receive control information from SITL'''
    try:
        buf = sim_in.recv(28)
    except socket.error as e:
        if not e.errno in [ errno.EAGAIN, errno.EWOULDBLOCK ]:
            raise
        return
        
    if len(buf) != 28:
        print('len=%u' % len(buf))
        return
    control = list(struct.unpack('<14H', buf))
    pwm = control[0:11]

    # map steering and throttle to -1/1
    state.steering = (pwm[0]-1500)/500.0
    state.throttle = (pwm[2]-1500)/500.0

#    print("steering=%f throttle=%f pwm=%s" % (state.steering, state.throttle, str(pwm)))
    


def interpret_address(addrstr):
    '''interpret a IP:port string'''
    a = addrstr.split(':')
    a[1] = int(a[1])
    return tuple(a)

class ControlState:
    def __init__(self):
        # steering from -1 to 1, where -1 is left, 1 is right
        self.steering = 0
        # throttle from -1 to 1, where -1 is full reverse, 1 is full forward
        self.throttle = 0

##################
# main program
from optparse import OptionParser
parser = OptionParser("sim_rover.py [options]")
parser.add_option("--simin",  dest="simin",   help="SIM input (IP:port)",       default="127.0.0.1:5502")
parser.add_option("--simout", dest="simout",  help="SIM output (IP:port)",      default="127.0.0.1:5501")
parser.add_option("--home", dest="home",  type='string', default=None, help="home lat,lng,alt,hdg (required)")
parser.add_option("--rate", dest="rate", type='int', help="SIM update rate", default=100)
parser.add_option("--skid-steering", action='store_true', default=False, help="Use skid steering")

(opts, args) = parser.parse_args()

for m in [ 'home' ]:
    if not opts.__dict__[m]:
        print("Missing required option '%s'" % m)
        parser.print_help()
        sys.exit(1)

# UDP socket addresses
sim_out_address = interpret_address(opts.simout)
sim_in_address  = interpret_address(opts.simin)

# setup input from SITL
sim_in = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sim_in.bind(sim_in_address)
sim_in.setblocking(0)

# setup output to SITL
sim_out = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sim_out.connect(sim_out_address)
sim_out.setblocking(0)

# create the quadcopter model
a = Rover(skid_steering=opts.skid_steering)

# initial controls state
state = ControlState()


# parse home
v = opts.home.split(',')
if len(v) != 4:
    print("home should be lat,lng,alt,hdg")
    sys.exit(1)
a.home_latitude  = float(v[0])
a.home_longitude = float(v[1])
a.home_altitude  = float(v[2])
a.altitude       = a.home_altitude
a.yaw            = float(v[3])
a.latitude = a.home_latitude
a.longitude = a.home_longitude

a.set_yaw_degrees(a.yaw)

print("Starting at lat=%f lon=%f alt=%f heading=%.1f" % (
    a.home_latitude,
    a.home_longitude,
    a.altitude,
    a.yaw))

frame_time = 1.0/opts.rate
sleep_overhead = 0

while True:
    frame_start = time.time()
    sim_recv(state)
    a.update(state)
    sim_send(a)
    t = time.time()
    frame_end = time.time()
    if frame_end - frame_start < frame_time:
        dt = frame_time - (frame_end - frame_start)
        dt -= sleep_overhead
        if dt > 0:
            time.sleep(dt)
        sleep_overhead = 0.99*sleep_overhead + 0.01*(time.time() - frame_end)
