#!/usr/bin/env python3
import json
import os
import unittest
import random
from PIL import Image, ImageDraw, ImageFont

from cereal import log, car
from common.basedir import BASEDIR
from common.params import Params
from selfdrive.controls.lib.events import Alert, EVENTS
from selfdrive.controls.lib.alertmanager import set_offroad_alert

AlertSize = log.ControlsState.AlertSize

OFFROAD_ALERTS_PATH = os.path.join(BASEDIR, "selfdrive/controls/lib/alerts_offroad.json")

# TODO: add callback alerts
ALERTS = []
for event_types in EVENTS.values():
  for alert in event_types.values():
    if isinstance(alert, Alert):
      ALERTS.append(alert)


class TestAlerts(unittest.TestCase):

  @classmethod
  def setUpClass(cls):
    with open(OFFROAD_ALERTS_PATH) as f:
      cls.offroad_alerts = json.loads(f.read())

  def test_events_defined(self):
    # Ensure all events in capnp schema are defined in events.py
    events = car.CarEvent.EventName.schema.enumerants

    for name, e in events.items():
      if not name.endswith("DEPRECATED"):
        fail_msg = "%s @%d not in EVENTS" % (name, e)
        self.assertTrue(e in EVENTS.keys(), msg=fail_msg)

  # ensure alert text doesn't exceed allowed width
  def test_alert_text_length(self):
    font_path = os.path.join(BASEDIR, "selfdrive/assets/fonts")
    regular_font_path = os.path.join(font_path, "opensans_semibold.ttf")
    bold_font_path = os.path.join(font_path, "opensans_semibold.ttf")
    semibold_font_path = os.path.join(font_path, "opensans_semibold.ttf")

    max_text_width = 1920 - 300  # full screen width is useable, minus sidebar
    # TODO: get exact scale factor. found this empirically, works well enough
    font_scale_factor = 1.85  # factor to scale from nanovg units to PIL

    draw = ImageDraw.Draw(Image.new('RGB', (0, 0)))

    fonts = {
      AlertSize.small: [ImageFont.truetype(semibold_font_path, int(40 * font_scale_factor))],
      AlertSize.mid: [ImageFont.truetype(bold_font_path, int(48 * font_scale_factor)),
                      ImageFont.truetype(regular_font_path, int(36 * font_scale_factor))],
    }

    for alert in ALERTS:
      # for full size alerts, both text fields wrap the text,
      # so it's unlikely that they  would go past the max width
      if alert.alert_size in [AlertSize.none, AlertSize.full]:
        continue

      for i, txt in enumerate([alert.alert_text_1, alert.alert_text_2]):
        if i >= len(fonts[alert.alert_size]):
          break

        font = fonts[alert.alert_size][i]
        w, _ = draw.textsize(txt, font)
        msg = "type: %s msg: %s" % (alert.alert_type, txt)
        self.assertLessEqual(w, max_text_width, msg=msg)

  def test_alert_sanity_check(self):
    for a in ALERTS:
      if a.alert_size == AlertSize.none:
        self.assertEqual(0, len(a.alert_text_1))
        self.assertEqual(0, len(a.alert_text_2))
      else:
        if a.alert_size == AlertSize.small:
          self.assertEqual(0, len(a.alert_text_2))

      self.assertTrue(all([n >= 0. for n in [a.duration_sound, a.duration_hud_alert, a.duration_text]]))

  def test_offroad_alerts(self):
    params = Params()
    for a in self.offroad_alerts:
      # set the alert
      alert = self.offroad_alerts[a]
      set_offroad_alert(a, True)
      self.assertTrue(json.dumps(alert) == params.get(a, encoding='utf8'))

      # then delete it
      set_offroad_alert(a, False)
      self.assertTrue(params.get(a) is None)

  def test_offroad_alerts_extra_text(self):
    params = Params()
    for i in range(50):
      # set the alert
      a = random.choice(list(self.offroad_alerts))
      alert = self.offroad_alerts[a]
      set_offroad_alert(a, True, extra_text="a"*i)

      expected_txt = alert['text'] + "a"*i
      written_txt = json.loads(params.get(a, encoding='utf8'))['text']
      self.assertTrue(expected_txt == written_txt)

if __name__ == "__main__":
  unittest.main()
