#!/usr/bin/python

'''
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as 
published by the Free Software Foundation, either version 3 of the 
License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

Changed TCSAFLUSH to TCSANOW for Android 8
'''

import sys
import os
import mod_globals

# Snippet from http://home.wlu.edu/~levys/software/kbhit.py

# Windows
if os.name == 'nt':
    import msvcrt

# Posix (Linux, OS X)
else:
    import termios
    import atexit
    from select import select
    from decimal import *

class KBHit:
    
    def __init__(self):
        '''Creates a KBHit object that you can call to do various keyboard things.
        '''

        if os.name == 'nt':
            pass
        
        else:
    
            # Save the terminal settings
            self.fd = sys.stdin.fileno()
            self.new_term = termios.tcgetattr(self.fd)
            self.old_term = termios.tcgetattr(self.fd)
    
            # New terminal setting unbuffered
            self.new_term[3] = (self.new_term[3] & ~termios.ICANON & ~termios.ECHO)
            termios.tcsetattr(self.fd, termios.TCSANOW, self.new_term)
    
            # Support normal-terminal reset at exit
            atexit.register(self.set_normal_term)
    
    
    def set_normal_term(self):
        ''' Resets to normal terminal.  On Windows this is a no-op.
        '''
        
        if os.name == 'nt':
            pass
        
        else:
            termios.tcsetattr(self.fd, termios.TCSANOW, self.old_term)


    def getch(self):
        ''' Returns a keyboard character after kbhit() has been called.
            Should not be called in the same program as getarrow().
        '''
        
        s = ''
        
        if os.name == 'nt':
            s = msvcrt.getch().decode('utf-8','ignore')        
        else:
            s = sys.stdin.read(1)

        if len(s)==0 or ord(s)==0 or ord(s)==0xe0:
          if os.name == 'nt':
            s = msvcrt.getch().decode('utf-8','ignore')        
          else:
            s = sys.stdin.read(1)

        return s 
                        

    def getarrow(self):
        ''' Returns an arrow-key code after kbhit() has been called. Codes are
        0 : up
        1 : right
        2 : down
        3 : left
        Should not be called in the same program as getch().
        '''
        
        if os.name == 'nt':
            msvcrt.getch() # skip 0xE0
            c = msvcrt.getch()
            vals = [72, 77, 80, 75]
            
        else:
            c = sys.stdin.read(3)[2]
            vals = [65, 67, 66, 68]
        
        return vals.index(ord(c.decode('utf-8')))
        

    def kbhit(self):
        ''' Returns True if keyboard character was hit, False otherwise.
        '''
        if os.name == 'nt':
            return msvcrt.kbhit()
        
        else:
            try:
                dr,dw,de = select([sys.stdin], [], [], 0)
            except:
                pass
            return dr != []
     
def Choice(list, question ):
  '''Util for make simple choice'''
  d = {};
  c = 1
  exitNumber = 0
  for s in list:
    if s.lower()=='<up>' or s.lower()=='<exit>':
      exitNumber = c
      print "%-2s - %s" % ('Q', pyren_encode(s))
      d['Q']=s
    else:
      print "%-2s - %s" % (c, pyren_encode(s))
      d[str(c)]=s
    c = c+1
  
  while (True):
    try:
      ch = raw_input(question)
    except (KeyboardInterrupt, SystemExit):
        print
        print 
        sys.exit()
    if ch=='q': ch = 'Q'
    if ch=='cmd': mod_globals.opt_cmd = True
    if ch in d.keys():
      return [d[ch],ch]

def ChoiceLong(list, question, header = '' ):
  '''Util for make choice from long list'''
  d = {};
  c = 1
  exitNumber = 0
  page = 0
  page_size = 20

  for s in list:
    if s.lower()=='<up>' or s.lower()=='<exit>':
      exitNumber = c
      d['Q']=s
    else:
      d[str(c)]=s
    c = c+1

  while( 1 ):

    clearScreen()
    #os.system('cls' if os.name == 'nt' else 'clear')      # clear screen
    #print chr(27)+"[2J"+chr(27)+"[;H",                    # clear ANSI screen (thanks colorama for windows)

    if len( header ): print pyren_encode(header)

    c = page*page_size 
    for s in list[page*page_size:(page+1)*page_size]:
      c = c + 1
      if s.lower()=='<up>' or s.lower()=='<exit>':
        print "%-2s - %s" % ('Q', pyren_encode(s))
      else:
        print "%-2s - %s" % (c, pyren_encode(s))

    if len(list)>page_size:
      if page>0:
        print "%-2s - %s" % ('P', '<prev page>')
      if (page+1)*page_size<len(list):
        print "%-2s - %s" % ('N', '<next page>')
      
    while (True):
      try:
        ch = raw_input(question)
      except (KeyboardInterrupt, SystemExit):
        print
        print 
        sys.exit()
      
      if ch=='q': ch = 'Q'
      if ch=='p': ch = 'P'
      if ch=='n': ch = 'N'
      
      if ch=='N' and (page+1)*page_size<len(list):
        page = page + 1
        break
      if ch=='P' and page>0:
        page = page - 1
        break

      if ch=='cmd': mod_globals.opt_cmd = True
      if ch in d.keys():
        return [d[ch],ch]

def ChoiceFromDict(dict, question, showId = True ):
  '''Util for make choice from dictionary'''
  d = {};
  c = 1
  exitNumber = 0
  for k in sorted(dict.keys()):
    s = dict[k]
    if k.lower()=='<up>' or k.lower()=='<exit>':
      exitNumber = c
      print "%s - %s" % ('Q',pyren_encode(s))
      d['Q']=k
    else:
      if showId:
        print "%s - (%s) %s" % (c,pyren_encode(k),pyren_encode(s))
      else:
        print "%s - %s" % (c,pyren_encode(s))      
      d[str(c)]=k
    c = c+1
  
  while (True):
    try:
      ch = raw_input(question)
    except (KeyboardInterrupt, SystemExit):
        print
        print 
        sys.exit()
    if ch=='q': ch = 'Q'
    if ch in d.keys():
      return [d[ch],ch]
      
def pyren_encode( inp ):
  if mod_globals.os == 'android':
    return inp.encode('utf-8', errors='replace')
  else:
    return inp.encode(sys.stdout.encoding, errors='replace')

def pyren_decode( inp ):
  if mod_globals.os == 'android':
    return inp.decode('utf-8', errors='replace')
  else:
    return inp.decode(sys.stdout.encoding, errors='replace')
    
def pyren_decode_i( inp ):
  if mod_globals.os == 'android':
    return inp.decode('utf-8', errors='ignore')
  else:
    return inp.decode(sys.stdout.encoding, errors='ignore')
    
def clearScreen():
  # https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences
  # [2J   - clear entire screen
  # [x;yH - move cursor to x:y
  sys.stdout.write(chr(27)+"[2J"+chr(27)+"[;H")

def upScreen():
  sys.stdout.write(chr(27)+"[;H")
    
def hex_VIN_plus_CRC( VIN ):
  '''The VIN must be composed of 17 alphanumeric characters apart from "I" and "O"'''
  
  #VIN    ='VF1LM1B0H11111111'
  VIN = VIN.upper()
  hexVIN = ''
  CRC    = 0xFFFF

  for c in VIN:                # for every byte in VIN
    b = ord(c)                 # get ASCII
    hexVIN = hexVIN + hex(b)[2:].upper()
    for i in range( 8 ):       # for every bit
      if ((CRC ^ b) & 0x1):    
        CRC = CRC >> 1
        CRC = CRC ^ 0x8408
        b = b >> 1
      else:
        CRC = CRC >> 1
        b = b >> 1

  # invert
  CRC = CRC ^ 0xFFFF

  # swap bytes
  b1 = (CRC >> 8) & 0xFF
  b2 = CRC & 0xFF
  CRC = ((b2 << 8) | b1) & 0xFFFF

  # result
  return hexVIN+hex( CRC )[2:].upper()

# Test    
if __name__ == "__main__":
    
    kb = KBHit()

    print('Hit any key, or ESC to exit')

    while True:

        if kb.kbhit():
            c = kb.getch()
            if ord(c) == 27: # ESC
                break
            print(c)
             
    kb.set_normal_term()

# Convert ASCII to HEX

def ASCIITOHEX( ATH ):
      
      ATH = ATH.upper()
      hexATH = ''.join("{:02x}".format(ord(c)) for c in ATH)
      
  #Result
      return hexATH

# Convert ch str to int then to Hexadecimal digits

def StringToIntToHex(DEC):

      DEC = int(DEC)
      hDEC = hex(DEC)

   #Result                         
      return hDEC[2:].zfill(2).upper()
      
def loadDumpToELM( ecuname, elm ): 
  ecudump = {}
  dumpname = ''
  
  flist = []
  for root, dirs, files in os.walk("./dumps"):
    for f in files:
      if (ecuname+'.') in f:
        flist.append(f)
  
  if len(flist)==0: return
  flist.sort()
  dumpname = os.path.join("./dumps/", flist[-1])
  
  #debug
  print "Loading:", dumpname
  
  df = open(dumpname,'rt')
  lines = df.readlines()
  df.close()
  
  for l in lines:
    l = l.strip().replace('\n','')
    if ':' in l:
      req,rsp = l.split(':')
      ecudump[req] = rsp
  
  elm.setDump( ecudump )


def getVIN( de, elm ):
  ''' getting VINs from every ECU     '''
  '''    de  - list of detected ECUs  '''
  '''    elm - reference to ELM class '''
  
  m_vin = {}
  for e in de:
  
    # init elm
    if mod_globals.opt_demo: #try to load dump
      loadDumpToELM( e['ecuname'], elm )
    else:
      if e['pin'].lower()=='can':
        elm.init_can()
        elm.set_can_addr( e['dst'], e )
      else:
        elm.init_iso()
        elm.set_iso_addr( e['dst'], e )
      elm.start_session( e['startDiagReq'] )
     
    # read VIN
    if e['stdType'].lower()=='uds':
      rsp = elm.request( req = '22F190', positive = '62', cache = False )[9:59]
    else:
      rsp = elm.request( req = '2181', positive = '61', cache = False )[6:56]
      
    vin = rsp.replace(' ','').decode('HEX')
    
    if len(vin)==17:
      m_vin[vin] = ''
      
  l_vin = m_vin.keys()
  
  if os.path.exists('savedVIN.txt'):
    with open('savedVIN.txt') as vinfile:
      vinlines = vinfile.readlines()
      for l in vinlines:
        l = l.strip()
        if '#' in l: continue
        if len(l)==17:
          l_vin.append(l.upper())

  if len(l_vin)==0:
    print "ERROR!!! Can't find any VIN. Check connection"
    exit()
  
  if len(l_vin)<2:
    return l_vin[0]
  
  print "\nFound ",len(l_vin), " VINs\n"
    
  choice = Choice(l_vin, "Choose VIN : ")
  
  return choice[0]
