#!/usr/bin/python


import threading
import time
import pickle
import traceback

from pycomm.ab_comm.clx import Driver as ClxDriver
from collections import deque
from device_base import deviceBase

PLC_IP_ADDRESS = '192.168.1.10'


def read_tag(addr, tag):
    """Read a tag from the PLC."""
    plc = ClxDriver()
    if plc.open(addr):
        try:
            v = plc.read_tag(tag)
            # print(v)
            return v
        except Exception:
            err = plc.get_status()
            plc.close()
            print err
        plc.close()


def read_array(addr, tag, start, end):
    """Read an array from the PLC."""
    plc = ClxDriver()
    if plc.open(addr):
        arr_vals = []
        try:
            for i in range(start, end):
                tag_w_index = tag + "[{}]".format(i)
                v = plc.read_tag(tag_w_index)
                # print('{} - {}'.format(tag_w_index, v))
                arr_vals.append(round(v[0], 4))
            # print(v)
            if arr_vals:
                return arr_vals
            else:
                print("No length for {}".format(addr))
                return False
        except Exception:
            err = plc.get_status()
            plc.close()
            print err
        plc.close()


def write_tag(addr, tag, val):
    """Write a tag to the PLC."""
    plc = ClxDriver()
    if plc.open(addr):
        try:
            cv = plc.read_tag(tag)
            wt = plc.write_tag(tag, val, cv[1])
            return wt
        except Exception:
            err = plc.get_status()
            plc.close()
            print err
        plc.close()


class Card(object):
    """Card class definition."""

    def __init__(self, unified_time):
        """Initialize the Card."""
        self.sc = []
        self.dc = []
        self.sent = False
        self.read_time = unified_time
        self.readCard()
        self.card_id = 0
        self.num_points = 0

    def readCard(self):
        """Read the card data."""
        self.card_id = read_tag(PLC_IP_ADDRESS, "Card_Past[1].ID")[0]
        self.num_points = int(read_tag(PLC_IP_ADDRESS, "Card_Past[1].Num_Points")[0])
        print("reading {} from card ID {}".format(self.num_points, self.card_id))

        if self.num_points > 1:
            surf_pos = False
            while not surf_pos:
                surf_pos = read_array(PLC_IP_ADDRESS, 'Card_Past[1].Surface_Position', 1, self.num_points)

            surf_lod = False
            while not surf_lod:
                surf_lod = read_array(PLC_IP_ADDRESS, 'Card_Past[1].Surface_Load', 1, self.num_points)

            down_pos = False
            while not down_pos:
                down_pos = read_array(PLC_IP_ADDRESS, 'Card_Past[1].Downhole_Position', 1, self.num_points)

            down_lod = False
            while not down_lod:
                down_lod = read_array(PLC_IP_ADDRESS, 'Card_Past[1].Downhole_Load', 1, self.num_points)

            if surf_pos and surf_lod and down_pos and down_lod:
                for i in range(0, self.num_points-1):
                    if not (surf_pos[i] == 0.0) and not (surf_lod[i] == 0.0):
                        self.sc.append([surf_pos[i], surf_lod[i]])
                    if not (down_pos[i] == 0.0) and not (down_lod[i] == 0.0):
                        self.dc.append([down_pos[i], down_lod[i]])
                return True
            else:
                print("couldn't get a full set of position/load pairs")
                print("Here's what we got:")
                print("SURFACE POS")
                print(surf_pos)
                print("SURFACE LOAD")
                print(surf_lod)
                print("DOWNHOLE POS")
                print(down_pos)
                print("DOWNHOLE LOAD")
                print(down_lod)
                return False

    def stringify(self):
        ''' returns a list of two strings [surface card, downhole card]'''
        # sc_str = "["
        # dc_str = "["
        # for i in range(0, len(self.sc)):
        #     this_sc_pair = self.sc[i]
        #     try:
        #         sc_str = sc_str + "[{},{}],".format(this_sc_pair[0], this_sc_pair[1])
        #     except IndexError, e:
        #         print("IndexError: {}\nFor {}".format(e, this_sc_pair))

        # for j in range(0, len(self.dc)):
        #     dc_str = dc_str + "[{},{}],".format(self.dc[j][0], self.dc[j][1])
        # dc_str = dc_str + "[{},{}]]".format(self.dc[0][0], self.dc[0][1])
        sc_str = str(self.sc)
        dc_str = str(self.dc)
        return[sc_str, dc_str]


class start(threading.Thread, deviceBase):

    def __init__(self, name=None, number=None, mac=None, Q=None, mcu=None, companyId=None, offset=None, mqtt=None, Nodes=None):
        threading.Thread.__init__(self)
        deviceBase.__init__(self, name=name, number=number, mac=mac, Q=Q, mcu=mcu, companyId=companyId, offset=offset, mqtt=mqtt, Nodes=Nodes)

        self.daemon = True
        self.version = "5"
        self.finished = threading.Event()
        threading.Thread.start(self)

        self.forceSend = False
        self.cardLoopTimer = 600
        self.statusChanged = False
        self.al_status_last = False
        self.dl_status_last = False
        self.card_storage_limit = 5
        self.last_card_sent_time = 0
        self.runLoopStatus = ""
        self.eventIds = []
        self.wellSetup = []

        # load stored event ID's
        try:
            with open('eventIds.p', 'rb') as handle:
                self.eventIds = pickle.load(handle)

            print "found pickled eventID dictionary: {0}".format(self.eventIds)
        except Exception:
            print "couldn't load enent ID's from pickle"
            

        # load stored wellconfig's
        try:
            with open('wellSetup.p', 'rb') as handle:
                self.wellSetup = pickle.load(handle)
            print "Found pickled Well Setup (but it's going to be too long to print)"
        except Exception:
            print "couldn't load Well Setup from pickle"

    # this is a required function for all drivers, its goal is to upload some piece of data
    # about your device so it can be seen on the web
    def register(self):
        pass


    def run(self):
        wait_sec = 30
        for i in range(0, wait_sec):
            print("poc driver will start in {} seconds".format(wait_sec - i))
            time.sleep(1)

        
        card_storage = deque([])  # array of the last x cards
        while True:
            if self.forceSend:
                print("FORCE SEND: TRUE")
            try:
                self.statusChanged = False

                self.runLoopStatus = "Reading Cards"
                if len(card_storage) > 0:
                    if not read_tag(PLC_IP_ADDRESS, "Card_Past[1].ID")[0] == card_storage[0].card_id:
                        current_time = time.time()
                        current_card = Card(current_time)
                        self.sendtodbDevJSON(1, "card_history", current_card.card_id, current_time, 'poc')
                        if (current_card.read_time - self.last_card_sent_time) > self.cardLoopTimer or self.forceSend:
                            cards = current_card.stringify()
                            self.sendtodbDev(1, 'sc', cards[0], current_time, "poc")
                            self.sendtodbDev(1, 'dc', cards[1], current_time, "poc")
                            self.last_card_sent_time = time.time()
                            current_card.sent = True
                        card_storage.appendleft(current_card)
                        while len(card_storage) > self.card_storage_limit:
                            card_storage.pop()
                        if self.statusChanged:
                            for c in card_storage:
                                if not c.sent:
                                    cstr = c.stringify()
                                    self.sendtodbDev(1, 'sc', cstr[0], c.read_time, "poc")
                                    self.sendtodbDev(1, 'dc', cstr[1], c.read_time, "poc")
                                    self.last_card_sent_time = time.time()
                else:
                    current_time = time.time()
                    current_card = Card(current_time)
                    self.sendtodbDevJSON(1, "card_history", current_card.card_id, current_time, 'poc')
                    if (current_card.read_time - self.last_card_sent_time) > self.cardLoopTimer or self.forceSend:
                        cards = current_card.stringify()
                        self.sendtodbDev(1, 'sc', cards[0], current_time, "poc")
                        self.sendtodbDev(1, 'dc', cards[1], current_time, "poc")
                        self.last_card_sent_time = time.time()
                        current_card.sent = True
                    card_storage.appendleft(current_card)
                self.runLoopStatus = "Complete"
                time.sleep(3)
                self.forceSend = False
            except Exception, e:
                sleep_timer = 20
                print("Error during {0} of run loop: {1}\nWill try again in {2} seconds...".format(self.runLoopStatus, e, sleep_timer))
                traceback.print_exc()
                time.sleep(sleep_timer)


    def poc_sync(self, name, value):
        self.sendtodb(1, "connected", "true", 0, 'poc')
        return True

    def poc_refresh_data(self, name, value):
        self.forceSend = True
        return True
