import pathlib
import os
import sys
import serial.tools.list_ports
from tkinter import filedialog
from tkinter import messagebox
from cview_log import *

MODE_TEXT = False
MODE_BIN = True
MODE_TEXT_STR = "Text"
MODE_BIN_STR = "Binary"
LI_POWER_ON = True
LI_POWER_OFF = False
LI_POWER_ON_STR = "On"
LI_POWER_OFF_STR = "Off"
PLOT_OFF = int(0)
PLOT_ON = int(1)
PLOT_UPDATING = int(2)
STREAM_LOG_EN = True
STREAM_LOG_DI = False
STREAM_LOG_EN_STR = "Enable"
STREAM_LOG_DI_STR = "Disable"
STREAM_PASSIVE_EN = True
STREAM_PASSIVE_DI = False
STREAM_PASSIVE_EN_STR = "Enable"
STREAM_PASSIVE_DI_STR = "Disable"
SER_B9600 = int(9600)
SER_B19200 = int(19200)
CRON_EN_STR = "Enable"
CRON_DI_STR = "Disable"
CRON_EN = True
CRON_DI = False
SEND_ESC_STR = "Send"
MODE_TEXT = False
PRESET_DIR_POS = True
PRESET_DIR_NEG = False
PRESET_DIR_POS_STR = "+"
PRESET_DIR_NEG_STR = "-"

class Observable:
    def __init__(self, initialValue=None):
        self.data = initialValue
        self.callbacks = {}

    def addCallback(self, func):
        self.callbacks[func] = 1

    def delCallback(self, func):
        del self.callback[func]

    def _docallbacks(self):
        for func in self.callbacks:
            func(self.data)

    def set(self, data):
        self.data = data
        self._docallbacks()

    def get(self):
        return self.data

    def unset(self):
        self.data = None

class Model:

    def __init__(self):

        if sys.platform.startswith("darwin"):
            self.device_dfl = "/dev/null"
            self.cwd_dfl = pathlib.Path().cwd()
            self.device_path_dfl = pathlib.Path('/dev')
            style_dfl="aqua"
        elif sys.platform.startswith("linux"):
            self.device_dfl = "/dev/null"
            self.cwd_dfl = pathlib.Path().cwd()
            self.device_path_dfl = pathlib.Path('/dev')
            style_dfl="default"
        elif sys.platform.startswith("cygwin"):
            self.device_dfl = "/dev/null"
            self.cwd_dfl = pathlib.Path().cwd()
            self.device_path_dfl = pathlib.Path('/dev')
            style_dfl="classic"
        elif sys.platform.startswith("win"):
            port_list = serial.tools.list_ports.comports()
            name_list = list()
            for port in port_list:
                name_list.append(port.name)
            cwg_log.debug("port names:"+str(name_list))
            self.device_dfl = port_list[0].name
            self.cwd_dfl = pathlib.Path().cwd()
            self.device_path_dfl = pathlib.Path('')
            style_dfl="winnative"
        else :
            cwg_log.error("unsupported OS: "+sys.platform)

        self.cwd = Observable()
        self.style = Observable(style_dfl)
        self.serial_device_dir = Observable(self.device_path_dfl)
        self.serial_device_list = Observable(list())
        self.serial_device = Observable(self.device_dfl)
        self.serial_connected = Observable(False)
        self.serial_busy = Observable(False)
        self.serial_speed_list = Observable(list())
        self.serial_speed = Observable(SER_B19200)
        self.upload_src = Observable("")
        self.upload_dest = Observable("")
        self.upload_mode = Observable(MODE_TEXT)
        self.download_src = Observable("")
        self.download_dest = Observable("")
        self.download_mode = Observable(MODE_BIN)
        self.li_power_state = Observable(LI_POWER_OFF)
        self.cron_state = Observable(CRON_EN)
        self.cwg_files = Observable(list())
        self.plot_state = Observable(int(PLOT_OFF))
        self.plot_period_sec = Observable(float(1.0))
        self.plot_points = Observable(int(20))
        self.plot_line_str = Observable("")
        self.plot_line_data = Observable(list())
        self.plot_stream_log = Observable(STREAM_LOG_DI)
        self.plot_stream_passive = Observable(STREAM_PASSIVE_DI)
        self.plot_clear = Observable(False)
        self.mode_test = Observable(False)
        self.preset_dir = Observable(True)
        self.debug = Observable(False)
        return

    def ValidateCWGFile(self,dest):
        retval = 1
        err = 0
        dot = dest.find(".")
        elen = len(dest[dot:])-1
        cwg_log.debug( "validating:"+dest )
        cwg_log.debug( "len:"+str(len(dest)) )
        cwg_log.debug( "dot:"+str(dot)+" elen:"+str(elen) )
        if len(dest) > 12 or len(dest) <= 0:
            msg = "invalid len ["+dest+"/"+str(len(dest))+"]"
            cwg_log.error(str(msg))
            retval = 0
            err = 1
        elif dot > 8 or (dot >= 0 and elen > 3 ):
            msg ="8.3 format required"
            cwg_log.error(msg)
            retval = 0
            err = 2
        else:
            for x in dest:
                y = str(x)
                if not ( y.isalnum() or y=="." or y=="-" or y=="_" or y=="~"):
                    cwg_log.debug("invalid char in filename ["+y+" in "+dest+"]")
                    retval = 0
                    err = 3
                    break;
        cwg_log.debug("valDes - ret"+str(retval)+"err:"+str(err))
        return retval

    def ValidateHostFile(self, src, testIsFile):
        retval = 1
        err = 0
        if len(src) <= 0:
            mag =  "len<=0 or len>12 ["+src+"/"+str(len(src))+"]"
            cwg_log.debug(str(msg))
            retval = 0
            err = 1
        elif testIsFile and not os.path.isfile(src):
            msg = "not a file ["+str(src)+"]"
            cwg_log.debug(msg)
            retval = 0
            err = 2
        cwg_log.debug("valSrc - ret:"+str(retval)+" err:"+str(err))
        return retval

    def ValidateDev(self, dev):
        retval = 1
        err = 0
        if len(dev) <= 0:
            msg = "invalid len ["+str(len(dev))+"]"
            cwg_log.debug(msg)
            retval = 0
            err = 1
        elif os.path.isfile(dev) or os.path.isdir(dev):
            if not sys.platform.startswith("win"):
                msg = "dev is file or dir ["+str(dev)+"/"+str(len(dev))+"]"
                cwg_log.debug(str(msg))
                retval = 0
                err = 2
        cwg_log.debug("valDev - ret:"+str(retval)+" err:"+str(err))
        return retval

    def ValidateDownload(self):
        retval = 1
        if self.ValidateCWGFile(self.download_src.get()) == 0 :
            retval = 0
        if self.ValidateHostFile(self.download_dest.get(), False) == 0 :
            retval = 0
        if self.ValidateDev(self.serial_device.get()) == 0 :
            retval = 0
        return retval

    def ValidateUpload(self):
        retval = 1
        if self.ValidateCWGFile(self.upload_dest.get()) == 0 :
            retval = 0
        if self.ValidateHostFile(self.upload_src.get(), True) == 0 :
            retval = 0
        if self.ValidateDev(self.serial_device.get()) == 0 :
            retval = 0
        return retval

    def updateSerialDevices(self):
        new_list = list()
        if not sys.platform.startswith("win"):
            for f_name in os.listdir(self.serial_device_dir.get()):
                if sys.platform.startswith("darwin") :
                    if f_name.startswith("cu.") or f_name.startswith("tty.") \
                    or f_name.startswith("usb") :
                        new_list.append(self.serial_device_dir.get() / f_name)
                elif sys.platform.startswith("cygwin") or sys.platform.startswith("linux") :
                    if f_name.startswith("cu") or f_name.startswith("tty") \
                    or f_name.startswith("usb") :
                        new_list.append(self.serial_device_dir.get() / f_name)
                else:
                    cwg_log.debug("unsupported platform:"+sys.platform)
        else:
            port_list =  serial.tools.list_ports.comports()
            for port in port_list:
                new_list.append(port.name)

        new_list.sort()
        self.serial_device_list.set(new_list)
        return

    def updateSerialDeviceDir(self):
        dirname = filedialog.askdirectory(initialdir = self.serial_device_dir,
                                              title = "Select a directory"
                                              )

        self.serial_device_dir.set(dirname)
        return

    def updateUploadSource(self):
        filename = filedialog.askopenfilename(initialdir = self.cwd.get(),
                                              title = "Select a File",
                                              filetypes = (("all files", "*.*"),
                                                           ("Text files","*.txt"),
                                                           ("Script files","*.scr")),
                                              )
        self.upload_src.set(filename)
        return

    def updateDownloadDest(self):
        filename = filedialog.askopenfilename(initialdir = self.cwd.get(),
                                          title = "Select a File",
                                          filetypes = (("all files", "*.*"),
                                                       ("Text files","*.txt"),
                                                       ("Script files","*.scr")),
                                          )
        self.download_dest.set(filename)
        return

