### old can parser just used by plant.py for regression tests
import os
import opendbc
from collections import defaultdict

from selfdrive.car.honda.hondacan import fix
from common.realtime import sec_since_boot
from common.dbc import dbc

class CANParser(object):
  def __init__(self, dbc_f, signals, checks=None):
    ### input:
    # dbc_f   : dbc file
    # signals : List of tuples (name, address, ival) where
    #             - name is the signal name.
    #             - address is the corresponding message address.
    #             - ival is the initial value.
    # checks  : List of pairs (address, frequency) where
    #             - address is the message address of a message for which health should be
    #               monitored.
    #             - frequency is the frequency at which health should be monitored.

    checks = [] if checks is None else checks
    self.msgs_ck = set([check[0] for check in checks])
    self.frqs = dict(checks)
    self.can_valid = False  # start with False CAN assumption
    # list of received msg we want to monitor counter and checksum for
    # read dbc file
    self.can_dbc = dbc(os.path.join(opendbc.DBC_PATH, dbc_f))
    # initialize variables to initial values
    self.vl = {}    # signal values
    self.ts = {}    # time stamp recorded in log
    self.ct = {}    # current time stamp
    self.ok = {}    # valid message?
    self.cn = {}    # message counter
    self.cn_vl = {} # message counter mismatch value
    self.ck = {}    # message checksum status

    for _, addr, _ in signals:
      self.vl[addr] = {}
      self.ts[addr] = {}
      self.ct[addr] = sec_since_boot()
      self.ok[addr] = True
      self.cn[addr] = 0
      self.cn_vl[addr] = 0
      self.ck[addr] = False

    for name, addr, ival in signals:
      self.vl[addr][name] = ival
      self.ts[addr][name] = 0

    self._msgs = [s[1] for s in signals]
    self._sgs = [s[0] for s in signals]

    self._message_indices = defaultdict(list)
    for i, x in enumerate(self._msgs):
      self._message_indices[x].append(i)

  def update_can(self, can_recv):
    msgs_upd = []
    cn_vl_max = 5   # no more than 5 wrong counter checks

    self.sec_since_boot_cached = sec_since_boot()

    # we are subscribing to PID_XXX, else data from USB
    for msg, ts, cdat, _ in can_recv:
      idxs = self._message_indices[msg]
      if idxs:
        msgs_upd.append(msg)
        # read the entire message
        out = self.can_dbc.decode((msg, 0, cdat))[1]
        # checksum check
        self.ck[msg] = True
        if "CHECKSUM" in out.keys() and msg in self.msgs_ck:
          # remove checksum (half byte)
          ck_portion = cdat[:-1] + chr(ord(cdat[-1]) & 0xF0)
          # recalculate checksum
          msg_vl = fix(ck_portion, msg)
          # compare recalculated vs received checksum
          if msg_vl != cdat:
            print "CHECKSUM FAIL: " + hex(msg)
            self.ck[msg] = False
            self.ok[msg] = False
        # counter check
        cn = 0
        if "COUNTER" in out.keys():
          cn = out["COUNTER"]
        # check counter validity if it's a relevant message
        if cn != ((self.cn[msg] + 1) % 4) and msg in self.msgs_ck and "COUNTER" in out.keys():
          #print hex(msg), "FAILED COUNTER!"
          self.cn_vl[msg] += 1   # counter check failed
        else:
          self.cn_vl[msg] -= 1   # counter check passed
        # message status is invalid if we received too many wrong counter values
        if self.cn_vl[msg] >= cn_vl_max:
          print "COUNTER WRONG: " + hex(msg)
          self.ok[msg] = False

        # update msg time stamps and counter value
        self.ct[msg] = self.sec_since_boot_cached
        self.cn[msg] = cn
        self.cn_vl[msg] = min(max(self.cn_vl[msg], 0), cn_vl_max)

        # set msg valid status if checksum is good and wrong counter counter is zero
        if self.ck[msg] and self.cn_vl[msg] == 0:
          self.ok[msg] = True

        # update value of signals in the
        for ii in idxs:
          sg = self._sgs[ii]
          self.vl[msg][sg] = out[sg]
          self.ts[msg][sg] = ts

    # for each message, check if it's too long since last time we received it
    self._check_dead_msgs()

    # assess overall can validity: if there is one relevant message invalid, then set can validity flag to False
    self.can_valid = True

    if False in self.ok.values():
      #print "CAN INVALID!"
      self.can_valid = False

    return msgs_upd

  def _check_dead_msgs(self):
    ### input:
    ## simple stuff for now: msg is not valid if a message isn't received for 10 consecutive steps
    for msg in set(self._msgs):
      if msg in self.msgs_ck and self.sec_since_boot_cached - self.ct[msg] > 10./self.frqs[msg]:
        self.ok[msg] = False
