#!/usr/bin/env python3
# pylint: skip-file
# type: ignore

import numpy as np
import math
from tqdm import tqdm
from typing import cast

import seaborn as sns
import matplotlib.pyplot as plt


from selfdrive.car.honda.interface import CarInterface
from selfdrive.car.honda.values import CAR
from selfdrive.controls.lib.vehicle_model import VehicleModel, create_dyn_state_matrices
from selfdrive.locationd.kalman.models.car_kf import CarKalman, ObservationKind, States

T_SIM = 5 * 60  # s
DT = 0.01


CP = CarInterface.get_params(CAR.CIVIC)
VM = VehicleModel(CP)

x, y = 0, 0  # m, m
psi = math.radians(0)  # rad

# The state is x = [v, r]^T
# with v lateral speed [m/s], and r rotational speed [rad/s]
state = np.array([[0.0], [0.0]])


ts = np.arange(0, T_SIM, DT)
speeds = 10 * np.sin(2 * np.pi * ts / 200.) + 25

angle_offsets = math.radians(1.0) * np.ones_like(ts)
angle_offsets[ts > 60] = 0
steering_angles = cast(np.ndarray, np.radians(5 * np.cos(2 * np.pi * ts / 100.)))

xs = []
ys = []
psis = []
yaw_rates = []
speed_ys = []


kf_states = []
kf_ps = []

kf = CarKalman()

for i, t in tqdm(list(enumerate(ts))):
  u = speeds[i]
  sa = steering_angles[i]
  ao = angle_offsets[i]

  A, B = create_dyn_state_matrices(u, VM)

  state += DT * (A.dot(state) + B.dot(sa + ao))

  x += u * math.cos(psi) * DT
  y += (float(state[0]) * math.sin(psi) + u * math.sin(psi)) * DT
  psi += float(state[1]) * DT

  kf.predict_and_observe(t, ObservationKind.CAL_DEVICE_FRAME_YAW_RATE, [float(state[1])])
  kf.predict_and_observe(t, ObservationKind.CAL_DEVICE_FRAME_XY_SPEED, [[u, float(state[0])]])
  kf.predict_and_observe(t, ObservationKind.STEER_ANGLE, [sa])
  kf.predict_and_observe(t, ObservationKind.ANGLE_OFFSET_FAST, [0])
  kf.predict(t)

  speed_ys.append(float(state[0]))
  yaw_rates.append(float(state[1]))
  kf_states.append(kf.x.copy())
  kf_ps.append(kf.P.copy())

  xs.append(x)
  ys.append(y)
  psis.append(psi)


xs = np.asarray(xs)
ys = np.asarray(ys)
psis = np.asarray(psis)
speed_ys = np.asarray(speed_ys)
kf_states = np.asarray(kf_states)
kf_ps = np.asarray(kf_ps)


palette = sns.color_palette()

def plot_with_bands(ts, state, label, ax, idx=1, converter=None):
  mean = kf_states[:, state].flatten()
  stds = np.sqrt(kf_ps[:, state, state].flatten())

  if converter is not None:
    mean = converter(mean)
    stds = converter(stds)

  sns.lineplot(ts, mean, label=label, ax=ax)
  ax.fill_between(ts, mean - stds, mean + stds, alpha=.2, color=palette[idx])


print(kf.x)

sns.set_context("paper")
f, axes = plt.subplots(6, 1)

sns.lineplot(ts, np.degrees(steering_angles), label='Steering Angle [deg]', ax=axes[0])
plot_with_bands(ts, States.STEER_ANGLE, 'Steering Angle kf [deg]', axes[0], converter=np.degrees)

sns.lineplot(ts, np.degrees(yaw_rates), label='Yaw Rate [deg]', ax=axes[1])
plot_with_bands(ts, States.YAW_RATE, 'Yaw Rate kf [deg]', axes[1], converter=np.degrees)

sns.lineplot(ts, np.ones_like(ts) * VM.sR, label='Steer ratio [-]', ax=axes[2])
plot_with_bands(ts, States.STEER_RATIO, 'Steer ratio kf [-]', axes[2])
axes[2].set_ylim([10, 20])


sns.lineplot(ts, np.ones_like(ts), label='Tire stiffness[-]', ax=axes[3])
plot_with_bands(ts, States.STIFFNESS, 'Tire stiffness kf [-]', axes[3])
axes[3].set_ylim([0.8, 1.2])


sns.lineplot(ts, np.degrees(angle_offsets), label='Angle offset [deg]', ax=axes[4])
plot_with_bands(ts, States.ANGLE_OFFSET, 'Angle offset kf deg', axes[4], converter=np.degrees)
plot_with_bands(ts, States.ANGLE_OFFSET_FAST, 'Fast Angle offset kf deg', axes[4], converter=np.degrees, idx=2)

axes[4].set_ylim([-2, 2])

sns.lineplot(ts, speeds, ax=axes[5])

plt.show()
