import os
import binascii
import itertools
import re
import struct
import subprocess
import random
from cereal import log

NetworkType = log.ThermalData.NetworkType
NetworkStrength = log.ThermalData.NetworkStrength

ANDROID = os.path.isfile('/EON')

def getprop(key):
  if not ANDROID:
    return ""
  return subprocess.check_output(["getprop", key], encoding='utf8').strip()

def get_imei(slot):
  slot = str(slot)
  if slot not in ("0", "1"):
    raise ValueError("SIM slot must be 0 or 1")

  ret = parse_service_call_string(service_call(["iphonesubinfo", "3" ,"i32", str(slot)]))
  if not ret:
    # allow non android to be identified differently
    ret = "%015d" % random.randint(0, 1<<32)
  return ret

def get_serial():
  ret = getprop("ro.serialno")
  if ret == "":
    ret = "cccccccc"
  return ret

def get_subscriber_info():
  ret = parse_service_call_string(service_call(["iphonesubinfo", "7"]))
  if ret is None or len(ret) < 8:
    return ""
  return ret

def reboot(reason=None):
  if reason is None:
    reason_args = ["null"]
  else:
    reason_args = ["s16", reason]

  subprocess.check_output([
    "service", "call", "power", "16", # IPowerManager.reboot
    "i32", "0", # no confirmation,
    *reason_args,
    "i32", "1" # wait
  ])

def service_call(call):
  if not ANDROID:
    return None

  ret = subprocess.check_output(["service", "call", *call], encoding='utf8').strip()
  if 'Parcel' not in ret:
    return None

  return parse_service_call_bytes(ret)

def parse_service_call_unpack(r, fmt):
  try:
    return struct.unpack(fmt, r)[0]
  except Exception:
    return None

def parse_service_call_string(r):
  try:
    r = r[8:] # Cut off length field
    r = r.decode('utf_16_be')

    # All pairs of two characters seem to be swapped. Not sure why
    result = ""
    for a, b, in itertools.zip_longest(r[::2], r[1::2], fillvalue='\x00'):
        result += b + a

    result = result.replace('\x00', '')

    return result
  except Exception:
    return None

def parse_service_call_bytes(ret):
  try:
    r = b""
    for hex_part in re.findall(r'[ (]([0-9a-f]{8})', ret):
      r += binascii.unhexlify(hex_part)
    return r
  except Exception:
    return None

def get_network_type():
  if not ANDROID:
    return NetworkType.none

  wifi_check = parse_service_call_string(service_call(["connectivity", "2"]))
  if wifi_check is None:
    return NetworkType.none
  elif 'WIFI' in wifi_check:
    return NetworkType.wifi
  else:
    cell_check = parse_service_call_unpack(service_call(['phone', '59']), ">q")
    # from TelephonyManager.java
    cell_networks = {
      0: NetworkType.none,
      1: NetworkType.cell2G,
      2: NetworkType.cell2G,
      3: NetworkType.cell3G,
      4: NetworkType.cell2G,
      5: NetworkType.cell3G,
      6: NetworkType.cell3G,
      7: NetworkType.cell3G,
      8: NetworkType.cell3G,
      9: NetworkType.cell3G,
      10: NetworkType.cell3G,
      11: NetworkType.cell2G,
      12: NetworkType.cell3G,
      13: NetworkType.cell4G,
      14: NetworkType.cell4G,
      15: NetworkType.cell3G,
      16: NetworkType.cell2G,
      17: NetworkType.cell3G,
      18: NetworkType.cell4G,
      19: NetworkType.cell4G
    }
    return cell_networks.get(cell_check, NetworkType.none)

def get_network_strength(network_type):
  network_strength = NetworkStrength.unknown
  # from SignalStrength.java
  def get_lte_level(rsrp, rssnr):
    INT_MAX = 2147483647
    lvl_rsrp = NetworkStrength.unknown
    lvl_rssnr = NetworkStrength.unknown
    if rsrp == INT_MAX:
      lvl_rsrp = NetworkStrength.unknown
    elif rsrp >= -95:
      lvl_rsrp = NetworkStrength.great
    elif rsrp >= -105:
      lvl_rsrp = NetworkStrength.good
    elif rsrp >= -115:
      lvl_rsrp = NetworkStrength.moderate
    else:
      lvl_rsrp = NetworkStrength.poor
    if rssnr == INT_MAX:
      lvl_rssnr = NetworkStrength.unknown
    elif rssnr >= 45:
      lvl_rssnr = NetworkStrength.great
    elif rssnr >= 10:
      lvl_rssnr = NetworkStrength.good
    elif rssnr >= -30:
      lvl_rssnr = NetworkStrength.moderate
    else:
      lvl_rssnr = NetworkStrength.poor
    return max(lvl_rsrp, lvl_rssnr)

  def get_tdscdma_level(tdscmadbm):
    lvl = NetworkStrength.unknown
    if tdscmadbm > -25:
      lvl = NetworkStrength.unknown
    elif tdscmadbm >= -49:
      lvl = NetworkStrength.great
    elif tdscmadbm >= -73:
      lvl = NetworkStrength.good
    elif tdscmadbm >= -97:
      lvl = NetworkStrength.moderate
    elif tdscmadbm >= -110:
      lvl = NetworkStrength.poor
    return lvl

  def get_gsm_level(asu):
    lvl = NetworkStrength.unknown
    if asu <= 2 or asu == 99:
      lvl = NetworkStrength.unknown
    elif asu >= 12:
      lvl = NetworkStrength.great
    elif asu >= 8:
      lvl = NetworkStrength.good
    elif asu >= 5:
      lvl = NetworkStrength.moderate
    else:
      lvl = NetworkStrength.poor
    return lvl

  def get_evdo_level(evdodbm, evdosnr):
    lvl_evdodbm = NetworkStrength.unknown
    lvl_evdosnr = NetworkStrength.unknown
    if evdodbm >= -65:
      lvl_evdodbm = NetworkStrength.great
    elif evdodbm >= -75:
      lvl_evdodbm = NetworkStrength.good
    elif evdodbm >= -90:
      lvl_evdodbm = NetworkStrength.moderate
    elif evdodbm >= -105:
      lvl_evdodbm = NetworkStrength.poor
    if evdosnr >= 7:
      lvl_evdosnr = NetworkStrength.great
    elif evdosnr >= 5:
      lvl_evdosnr = NetworkStrength.good
    elif evdosnr >= 3:
      lvl_evdosnr = NetworkStrength.moderate
    elif evdosnr >= 1:
      lvl_evdosnr = NetworkStrength.poor
    return max(lvl_evdodbm, lvl_evdosnr)

  def get_cdma_level(cdmadbm, cdmaecio):
    lvl_cdmadbm = NetworkStrength.unknown
    lvl_cdmaecio = NetworkStrength.unknown
    if cdmadbm >= -75:
      lvl_cdmadbm = NetworkStrength.great
    elif cdmadbm >= -85:
      lvl_cdmadbm = NetworkStrength.good
    elif cdmadbm >= -95:
      lvl_cdmadbm = NetworkStrength.moderate
    elif cdmadbm >= -100:
      lvl_cdmadbm = NetworkStrength.poor
    if cdmaecio >= -90:
      lvl_cdmaecio = NetworkStrength.great
    elif cdmaecio >= -110:
      lvl_cdmaecio = NetworkStrength.good
    elif cdmaecio >= -130:
      lvl_cdmaecio = NetworkStrength.moderate
    elif cdmaecio >= -150:
      lvl_cdmaecio = NetworkStrength.poor
    return max(lvl_cdmadbm, lvl_cdmaecio)

  if network_type == NetworkType.none:
    return network_strength
  if network_type == NetworkType.wifi:
    out = subprocess.check_output('dumpsys connectivity', shell=True).decode('ascii')
    network_strength = NetworkStrength.unknown
    for line in out.split('\n'):
      signal_str = "SignalStrength: "
      if signal_str in line:
        lvl_idx_start = line.find(signal_str) + len(signal_str)
        lvl_idx_end = line.find(']', lvl_idx_start)
        lvl = int(line[lvl_idx_start : lvl_idx_end])
        if lvl >= -50:
          network_strength = NetworkStrength.great
        elif lvl >= -60:
          network_strength = NetworkStrength.good
        elif lvl >= -70:
          network_strength = NetworkStrength.moderate
        else:
          network_strength = NetworkStrength.poor
    return network_strength
  else:
    # check cell strength
    out = subprocess.check_output('dumpsys telephony.registry', shell=True).decode('ascii')
    for line in out.split('\n'):
      if "mSignalStrength" in line:
        arr = line.split(' ')
        ns = 0
        if ("gsm" in arr[14]):
          rsrp = int(arr[9])
          rssnr = int(arr[11])
          ns = get_lte_level(rsrp, rssnr)
          if ns == NetworkStrength.unknown:
            tdscmadbm = int(arr[13])
            ns = get_tdscdma_level(tdscmadbm)
            if ns == NetworkStrength.unknown:
              asu = int(arr[1])
              ns = get_gsm_level(asu)
        else:
          cdmadbm = int(arr[3])
          cdmaecio = int(arr[4])
          evdodbm = int(arr[5])
          evdosnr = int(arr[7])
          lvl_cdma = get_cdma_level(cdmadbm, cdmaecio)
          lvl_edmo = get_evdo_level(evdodbm, evdosnr)
          if lvl_edmo == NetworkStrength.unknown:
            ns = lvl_cdma
          elif lvl_cdma == NetworkStrength.unknown:
            ns = lvl_edmo
          else:
            ns = min(lvl_cdma, lvl_edmo)
        network_strength = max(network_strength, ns)

    return network_strength