#!/usr/bin/python3
import serial
import time
import sys
import binascii
import terra_constants as tc
from bitstring import BitArray

class message(object):
    def __init__(self,code,length,content):
        self.code=code
        self.length=length
        self.content=content

def close(ser):
    """ close connection
    """
    ser.close()

        
def connect_and_flush():
    """ Connect to the board
    """
    ser = serial.Serial(port='/dev/ttyACM0', baudrate=115200, timeout=None)
    ser.close()
    ser.open()
    ser.flush()
    num=ser.inWaiting()
    dat=ser.read(num)
    return ser

def pause_board(ser):
    """ Pause board
    """
    mes=message(tc.MESSAGE_RUNNING_LOW,1,bytes([0x00]))
    write_message(ser,mes);
    msg=read_message(ser)

    while((not msg.code==tc.MESSAGE_OK) and (not msg.code==tc.MESSAGE_FAILED)):
        num=ser.inWaiting()
        dat=ser.read(num)
        mes=message(tc.MESSAGE_RUNNING_LOW,1,bytes([0]))
        write_message(ser,mes);
        msg=read_message(ser)

    if (msg.code==tc.MESSAGE_OK):
        print("Success.")
        print((msg.content).decode());
    elif (msg.code==tc.MESSAGE_FAILED):
        print("Operation failed.")
        print((msg.content).decode());
    else:
        print("Unknown response. ");
        print(format(msg.code))
        print("-")
        print(format(bytes([msg.length])))
        print(": ")
        print((msg.content).decode());
    print('')


def resume_board(ser):
    """ Resume after pause_board
    """
    mes=message(tc.MESSAGE_RUNNING_HIGH,(1),bytes([0]))
    write_message(ser,mes);
    msg=read_message(ser)

    i=10
    while(i>0 and not msg.code==tc.MESSAGE_OK):
        num=ser.inWaiting()
        dat=ser.read(num)
        mes=message(tc.MESSAGE_RUNNING_HIGH,(1),bytes([0]))
        write_message(ser,mes);
        msg=read_message(ser)
        i=i-1

    while(msg.code==tc.MESSAGE_FAILED_CHECKSUM):
        write_message(ser,mes);
        msg=read_message(ser);

    if (msg.code==tc.MESSAGE_OK):
        print("Success.")
        print((msg.content).decode());
    elif (msg.code==tc.MESSAGE_FAILED):
        print("Operation failed.")
        print((msg.content).decode());
    else:
        print("Unknown response. ");
        print(format(msg.code))
        print("-")
        print(format(bytes([msg.length])))
        print(": ")
        print((msg.content).decode());
    sys.stdout.flush();
    print('')


def read_message(ser):
    """ Read a single message from board
    """
    code=ser.read(1)
    temp=ser.read(1)
    length=ord(temp)
    content=[]
    for x in range(0,length):
        content.append(ord(ser.read(1)))
    msg=message(code,length,bytes(content))
    return msg

    
def write_message(ser,mes):
    """ Write a single message to board
    """
    cs=0;
    ser.write(mes.code)
    cs=cs+ord(mes.code);
    ser.write(bytes([mes.length]));
    cs=cs+mes.length;
    for x in range(0,mes.length):
        ser.write(bytes([mes.content[x]]));
        cs=cs+(mes.content[x]);
    ser.write(bytes([cs%256]));
    ser.flush();


def single_alarm(ser,onoff,floor,lamp,hour,min,sec):
    """ Send one timer to the board
    """
    if (onoff=='on'):
        msg=message(tc.MESSAGE_TIMER_ON,(5),bytes([floor,lamp,hour,min,sec]))
        write_message(ser,msg)
    elif (onoff=='off'):
        msg=message(tc.MESSAGE_TIMER_OFF,(5),bytes([floor,lamp,hour,min,sec]))
        write_message(ser,msg)
    else:
        print('invalid switch');
    msg=read_message(ser)
    if (msg.code==tc.MESSAGE_OK):
        print("Success.")
        print((msg.content).decode());
    elif (msg.code==tc.MESSAGE_FAILED):
        print("Operation failed.")
        print((msg.content).decode());
    else:
        print("Unknown response.");
        print(format(msg.code))
        print((msg.content).decode());
    sys.stdout.flush();
    print('')

    
def query_alarm(ser,floor,lamp):
    """ request timer details from the board
    """
    msg=message(tc.MESSAGE_REQUEST_ALARM_DATA,(2),bytes([floor,lamp]))
    write_message(ser,msg)

    msg=read_message(ser)
    if (msg.code==tc.MESSAGE_OK):
        print("Success.")
        print((msg.content).decode());
    elif (msg.code==tc.MESSAGE_FAILED):
        print("Operation failed.")
        print((msg.content).decode());
    else:
        print("Unknown response.");
        print(format(msg.code))
        print((msg.content).decode());
    sys.stdout.flush();
    print('')

    return msg.content

    
def remove_alarm(ser,floor,lamp,id_string):
    """ remove one timer from board
    """
    msg=message(tc.MESSAGE_DELETE_ALARM,(2+len(id_string)+1),bytes([floor,lamp])+id_string+bytes([0]))
    write_message(ser,msg)

    msg=read_message(ser)
    if (msg.code==tc.MESSAGE_OK):
        print("Success.")
        print((msg.content).decode());
    elif (msg.code==tc.MESSAGE_FAILED):
        print("Operation failed.")
        print((msg.content).decode());
    else:
        print("Unknown response.");
        print(format(msg.code))
        print((msg.content).decode());
    sys.stdout.flush();
    print('')


def long_to_bytes (val, endianness='big'):
    # one (1) hex digit per four (4) bits
    width = val.bit_length()

    # unhexlify wants an even multiple of eight (8) bits, but we don't
    # want more digits than we need (hence the ternary-ish 'or')
    width += 8 - ((width % 8) or 8)

    # format width specifier: four (4) bits per hex digit
    fmt = '%%0%dx' % (width // 4)

    # prepend zero (0) to the width, to zero-pad the output
    s = binascii.unhexlify(bytes(fmt % val,"UTF-8"))

    if endianness == 'little':
        # see http://stackoverflow.com/a/931095/309233
        s = s[::-1]
    return s

    
def make_date_message():
    """ upload current time to the board
        TODO: requires proper handling of the timezone
    """
    localtime = time.time()+60*60*2
    byte_localtime = long_to_bytes(int(round(localtime)))
    return byte_localtime

    
def start(ser):
    """ Initialize the board
    """
    byte_time_message=make_date_message()
    mes=message(tc.MESSAGE_SET_TIME,len(byte_time_message),byte_time_message)
    write_message(ser,mes);
    msg=read_message(ser);

    while(msg.code==tc.MESSAGE_FAILED_CHECKSUM):
        num=ser.inWaiting()
        dat=ser.read(num)
        write_message(ser,mes);
        msg=read_message(ser);
    
    if (msg.code==tc.MESSAGE_OK):
        print("Success.")
        print((msg.content).decode());
    elif (msg.code==tc.MESSAGE_FAILED):
        print("Operation failed.")
        print((msg.content).decode());
    else:
        print("Unknown response.");
        print(format(msg.code))
        print((msg.content).decode());
    sys.stdout.flush();
    print('')

    
def add_timer(ser,floor,lamp,hour,min,sec,state):
    """ add one timer
    """
    mes=message(tc.MESSAGE_ADD_TIMER,(6),bytes([floor,lamp,hour,min,sec,state]))
    write_message(ser,mes)
    msg=read_message(ser)
    
    while(msg.code==tc.MESSAGE_FAILED_CHECKSUM):
        write_message(ser,mes);
        msg=read_message(ser);

    if (msg.code==tc.MESSAGE_OK):
        print("Success.")
        print((msg.content).decode());
    elif (msg.code==tc.MESSAGE_FAILED):
        print("Operation failed.")
        print((msg.content).decode());
    else:
        print("Unknown response.");
        print(format(msg.code))
        print((msg.content).decode());
    sys.stdout.flush();
    
    print('')

    
def single_dim(ser,floor,lamp,lev):
    """ add one dimmer
    """
    msg=message(tc.MESSAGE_DIMMER,(3),bytes([floor,lamp,lev]));
    write_message(ser,msg)
    msg=read_message(ser)
    if (msg.code==tc.MESSAGE_OK):
        print("Success.")
        print((msg.content).decode());
    elif (msg.code==tc.MESSAGE_FAILED):
        print("Operation failed.")
        print((msg.content).decode());
    else:
        print("Unknown response.");
        print(format(msg.code[0]))
        print((msg.content).decode());
    sys.stdout.flush()
    print('')

    
def single_switch(ser,onoff,floor,lamp):
    """ switch lamp on/off
    """
    if (onoff=='on'):
        mes=message(tc.MESSAGE_SWITCH_ON,(2),bytes([floor,lamp]))
        write_message(ser,mes)
    elif (onoff=='off'):
        mes=message(tc.MESSAGE_SWITCH_OFF,(2),bytes([floor,lamp]))
        write_message(ser,mes)
    else:
        print('invalid switch');

    msg=read_message(ser)
    while(msg.code==tc.MESSAGE_FAILED_CHECKSUM):
        write_message(ser,mes);
        msg=read_message(ser);

    if (msg.code==tc.MESSAGE_OK):
        print("Success.")
        print((msg.content).decode());
    elif (msg.code==tc.MESSAGE_FAILED):
        print("Operation failed.")
        print((msg.content).decode());
    else:
        print("Unknown response.");
        print(format(msg.code))
        print((msg.content).decode());
    sys.stdout.flush()
    print('')
