#!/usr/bin/env python
# -*- coding: utf-8 -*-

#import requests

import mmap
import os

#import requests
import urllib
#from urllib.request import urlopen # for win 3.44 version
import urllib2     # for osx version

#import urllib.request

#from urllib.request import urlopen

#from __future__ import print_function
import socket
import sys
import os
import optparse
import logging
import hashlib
import random
import locale

#locale.setlocale(locale.LC_ALL, "")
#message_language = locale.getlocale(locale.LC_MESSAGES)[0]

import socket

#from tkinter import *		# for win 3.44 version
from Tkinter import *		# for osx version

#from Tkinter import filedialog
#import tkinter as filedialog    # for win 3.44 version
import tkFileDialog as filedialog    # for osx version

#from Tkinter import Tk, BOTH
#from ttk import Frame, Button, Style, Label

#from tkinter import Style		# for win 3.44 version
from ttk import Style		# for osx version

#import tkinter as mbox    # for win 3.44 version
import tkMessageBox as mbox    # for osx version




class AutoScrollbar(Scrollbar):
    # A scrollbar that hides itself if it's not needed.
    # Only works if you use the grid geometry manager!
    def set(self, lo, hi):
        if float(lo) <= 0.0 and float(hi) >= 1.0:
            # grid_remove is currently missing from Tkinter!
            self.tk.call("grid", "remove", self)
        else:
            self.grid()
        Scrollbar.set(self, lo, hi)
    def pack(self, **kw):
        raise TclError("cannot use pack with this widget")
    def place(self, **kw):
        raise TclError("cannot use place with this widget")



root = Tk()

vscrollbar = AutoScrollbar(root)
vscrollbar.grid(row=0, column=1, sticky=N+S)

hscrollbar = AutoScrollbar(root, orient=HORIZONTAL)
hscrollbar.grid(row=1, column=0, sticky=E+W)

#upperframe.pack(side="top", fill="both", expand=True, padx=4)
#lowerframe.pack(side="left", fill="both", expand=True, padx=4, pady=4)
#btnframe.pack(side="right", fill="both", padx=4, pady=4)

#top_canvas = Canvas(root, width = 100, height = 500)
#top_canvas = Canvas(root, yscrollcommand=vscrollbar.set, xscrollcommand=hscrollbar.set)
#top_canvas.grid(row=0, column=0)
#top_canvas.pack(side="top", fill="both", expand=True, padx=4)

#top_canvas.grid(row=0, column=0, sticky=N+S+E+W)

canvas = Canvas(root, yscrollcommand=vscrollbar.set, xscrollcommand=hscrollbar.set)
canvas.grid(row=0, column=0, sticky=N+S+E+W)



vscrollbar.config(command=canvas.yview)
hscrollbar.config(command=canvas.xview)

# make the canvas expandable
root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(0, weight=1)

# create canvas contents
#top_frame = Frame(top_canvas, width = 100, height = 500)
#top_frame.rowconfigure(1, weight=1)
#top_frame.columnconfigure(1, weight=1)
#top_frame.geometry("100x500")

frame = Frame(canvas)
frame.rowconfigure(1, weight=1)
frame.columnconfigure(1, weight=1)


#rows = 15
#for i in range(1, rows):
#    for j in range(1, 10):
#        button = Button(frame, text="%d, %d" % (i,j))
#        button.grid(row=i, column=j)
#        button.grid(row=i, column=j, sticky='news')

#canvas.create_window(0, 0, anchor=NW, window=frame)
#frame.update_idletasks()
#canvas.config(scrollregion=canvas.bbox("all"))

if os.name == 'nt':
	app_width = 580
else:
	app_width = 860

app_height = 500

FLASH = 0
SPIFFS = 100
AUTH = 200
PROGRESS = False

HW_BLOCK_UPDATE = 0
Flash_Block_Count = 0
Current_FW = 2.593

REAL_FLASHING = True

TEXT_String_APP_Label = "SWIITCH Firmware Updater"
TEXT_String_Update = "Обновить"
TEXT_String_From_File = "Из файла"
TEXT_String_From_Cloud = "Из облака"
TEXT_String_Device_Found = "Найдено устройств: "
TEXT_String_Firmware_Complete = "Обновление завершено"
TEXT_String_Update_Firmware = "Обновление прошивки"
TEXT_String_Sure_Update_Firmware = "Вы уверены что хотите обновить прошивку в"
TEXT_String_Error = "Ошибка"
TEXT_String_Error_download_firmware_for = "Нет новой версии для вашего устройства"
#TEXT_String_Error_download_firmware_for = "Ошибка скачивания прошивки для"
TEXT_String_From_file = "из файла"
TEXT_String_Write_block = "запись блока"
TEXT_String_NO_Result = "Ошибка обновления"
TEXT_String_File = "Файл"
TEXT_String_Not_firmware = "не файл прошивки"

#TEXT_String_Update = "Update"
#TEXT_String_File = "File"
#TEXT_String_Device_Found = "Device found: "

dev_mac = []
dev_ip = []
dev_label = []
dev_guest = []
dev_fw_version = []
dev_fw_version_cur = []
dev_hw_version = []

Label_DEV_Num = []
Label_DEV_Label = []
Label_DEV_MAC = []
Label_DEV_IP = []
Label_DEV_FW_Version = []
Label_DEV_FW_Version_Cur = []
Label_DEV_HW_Version = []
Button_DEV_Update = []
Button_DEV_Update_File = []


#---socket creation
connexion = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)


SCAN_Button = Button(root, text=TEXT_String_Device_Found + " 0", width=30)




def Check_File_Str(filename, find_str):
	print("begin check firmware file")
	f = open(filename)
	s = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)
	if s.find(find_str) != -1:
		print("file is firmware")
		return True
	else:
		print('file is NOT firmware')
		return False



def download_file(url, filename):
	try:
		urllib.urlretrieve(url, filename)
#		r = requests.get(url)
#		with open("filename", "wb") as code:
#			code.write(r.content)
#		print "Download Complete!"

#		response = urllib2.urlopen(url)
#		data = response.read()

#		file_ = open(filename, 'w')
#		file_.write(data)
#		file_.close()

		return True
	except:
		print("ERROOR DOWNLOAD File " + url)
	return False



def fetch_thing(url, params, method = 'GET'):
#    params = urllib.urlencode(params)
#    params = "ip_cmd=y&dnum=0&sw_=y&button_ns_press=y"

	if method=='POST':
#		f = urlopen(url, params) # for win 3.44 version
		f = urllib.urlopen(url, params) # for osx version
	else:
#		f = urlopen(url+'?'+params) # for win 3.44 version
		f = urllib.urlopen(url+'?'+params) # for osx version
	return (f.read(), f.code)


def Firmware_Flash(remoteAddr, localAddr, remotePort, localPort, password, filename, command = FLASH):
	global REAL_FLASHING
	global HW_BLOCK_UPDATE
	global Flash_Block_Count

	if HW_BLOCK_UPDATE:
		print("now already update firmware")
#	return

#     HW_BLOCK_UPDATE = True

#	dev_s_ap_start
#	/nevicom/swiitch/users
#	1234567809


	if Check_File_Str(filename, '/nevicom/swiitch/users') == False:
		print("this file not flash")
		mbox.showwarning(TEXT_String_Error, TEXT_String_File + " " + filename + " " + TEXT_String_Not_firmware + " !")
		return


	if REAL_FLASHING == True:
		Flash_Block_Count = 0
		content, response_code = fetch_thing('http://' + remoteAddr, "ip_cmd=y&dev_=y&ota_enable=on",'GET')
		print("ENABLE OTA http return " + content)

		serve(remoteAddr, localAddr, remotePort, localPort, password, filename, command)
		mbox.showinfo(TEXT_String_Update_Firmware, TEXT_String_Firmware_Complete)
	else:
		print("NO REAL FLASHING")


#    HW_BLOCK_UPDATE = False




def update_progress(progress):
  global Flash_Block_Count
  if (PROGRESS):
    barLength = 60 # Modify this to change the length of the progress bar
    status = ""
    if isinstance(progress, int):
      progress = float(progress)
    if not isinstance(progress, float):
      progress = 0
      status = "error: progress var must be float\r\n"
    if progress < 0:
      progress = 0
      status = "Halt...\r\n"
    if progress >= 1:
      progress = 1
      status = "Done...\r\n"
    block = int(round(barLength*progress))
    text = "\rUploading: [{0}] {1}% {2}".format( "="*block + " "*(barLength-block), int(progress*100), status)
    sys.stderr.write(text)
    sys.stderr.flush()
  else:
    root.title(TEXT_String_APP_Label + " " + TEXT_String_Write_block + " " + str(Flash_Block_Count))
    Flash_Block_Count = Flash_Block_Count + 1
    sys.stderr.write('.')
    sys.stderr.flush()






def serve(remoteAddr, localAddr, remotePort, localPort, password, filename, command = FLASH):
  # Create a TCP/IP socket
  sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  server_address = (localAddr, localPort)
  logging.info('Starting on %s:%s', str(server_address[0]), str(server_address[1]))
  try:
    sock.bind(server_address)
    sock.listen(1)
  except:
    logging.error("Listen Failed")
    HW_BLOCK_UPDATE = False
    return 1

  content_size = os.path.getsize(filename)
  f = open(filename,'rb')
  file_md5 = hashlib.md5(f.read()).hexdigest()
  f.close()
  logging.info('Upload size: %d', content_size)
  message = '%d %d %d %s\n' % (command, localPort, content_size, file_md5)

  # Wait for a connection
  logging.info('Sending invitation to: %s', remoteAddr)
  sock2 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
  remote_address = (remoteAddr, int(remotePort))
  sent = sock2.sendto(message.encode(), remote_address)
  sock2.settimeout(10)
  try:
    data = sock2.recv(128).decode()
  except:
    logging.error('No Answer')
    sock2.close()
    return 1
  if (data != "OK"):
    if(data.startswith('AUTH')):
      nonce = data.split()[1]
      cnonce_text = '%s%u%s%s' % (filename, content_size, file_md5, remoteAddr)
      cnonce = hashlib.md5(cnonce_text.encode()).hexdigest()
      passmd5 = hashlib.md5(password.encode()).hexdigest()
      result_text = '%s:%s:%s' % (passmd5 ,nonce, cnonce)
      result = hashlib.md5(result_text.encode()).hexdigest()
      sys.stderr.write('Authenticating...')
      sys.stderr.flush()
      message = '%d %s %s\n' % (AUTH, cnonce, result)
      sock2.sendto(message.encode(), remote_address)
      sock2.settimeout(10)
      try:
        data = sock2.recv(32).decode()
      except:
        sys.stderr.write('FAIL\n')
        logging.error('No Answer to our Authentication')
        sock2.close()
        HW_BLOCK_UPDATE = False
        return 1
      if (data != "OK"):
        sys.stderr.write('FAIL\n')
        logging.error('%s', data)
        sock2.close()
        sys.exit(1);
        return 1
      sys.stderr.write('OK\n')
    else:
      logging.error('Bad Answer: %s', data)
      sock2.close()
      return 1
  sock2.close()

  logging.info('Waiting for device...')
  try:
    sock.settimeout(10)
    connection, client_address = sock.accept()
    sock.settimeout(None)
    connection.settimeout(None)
  except:
    logging.error('No response from device')
    sock.close()
    return 1

  try:
    f = open(filename, "rb")
    if (PROGRESS):
      update_progress(0)
    else:
      sys.stderr.write('Uploading')
      sys.stderr.flush()
    offset = 0
    while True:
      chunk = f.read(1460)
      if not chunk: break
      offset += len(chunk)
      update_progress(offset/float(content_size))
      connection.settimeout(10)
      try:
        connection.sendall(chunk)
        res = connection.recv(4)
      except:
        sys.stderr.write('\n')
        logging.error('Error Uploading')
        connection.close()
        f.close()
        sock.close()
        return 1

    sys.stderr.write('\n')
    logging.info('Waiting for result...')
    # libraries/ArduinoOTA/ArduinoOTA.cpp L311 L320
    # only sends digits or 'OK'. We must not not close
    # the connection before receiving the 'O' of 'OK'
    try:
      connection.settimeout(60)
      while True:
        if connection.recv(32).decode().find('O') >= 0: break
      logging.info('Result: OK')
      connection.close()
      f.close()
      sock.close()
      if (data != "OK"):
        sys.stderr.write('\n')
        logging.error('%s', data)
        HW_BLOCK_UPDATE = False
        return 1;
      return 0
    except:
      logging.error('No Result!')
      root.title(TEXT_String_APP_Label + " " + TEXT_String_NO_Result)
      connection.close()
      f.close()
      sock.close()
      return 1

  finally:
    connection.close()
    f.close()

  sock.close()
  return 1
# end serve







def UDP_Init():
	try:
		connexion.bind(('', 21399))
		connexion.settimeout(1)
#		print("UDP Init ... ok")
	except socket.error:
		print("connexion failed")
		connexion.close()
		sys.exit()

#	connexion_send = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
#	connexion_send.sendto("Start SWIITCH Firmware updater", ("localhost", 21399))




def UDP_Listen():
	try:
#		print ("Start UDP Listen")
		data, addr = connexion.recvfrom(1024)
#		print "messages : ", data
	except:
		return

	tmp_mac = get_param_value(data, "mac_d_")
	tmp_mac = tmp_mac.replace(":", "")
	if (len(tmp_mac) < 10):
		print("this not swiitch modules")
		return
	tmp_ip = get_param_value(data, "ip_d_")
	tmp_label = get_param_value(data, "label_d_")
	tmp_guest = get_param_value(data, "guest_s_") == "on"
	tmp_version = get_param_value(data, "version_d_")

	tmp_fw_version = float(get_param_value_full(tmp_version, "Swiitch ", "("))
	tmp_hw_version = get_param_value(tmp_version, ") ")
	tmp_hw_version = tmp_hw_version.replace(" /P", "");
	tmp_hw_version = tmp_hw_version.replace(" /", "");

	found_device_num = -1

	for i in range(len(dev_mac)):
		if dev_mac[i] == tmp_mac:
			found_device_num = i

	if found_device_num == -1:
#		print("FOUND NEW DEVICE : " + str(len(dev_mac)+1) + " ip: " + tmp_ip + " mac: " + tmp_mac + " guest: " + bool_to_string(tmp_guest) + " update: " + bool_to_string(Current_FW > tmp_fw_version) + " fw: " + str(tmp_fw_version) + " hw: " + tmp_hw_version + " " + tmp_label)

		dev_mac.append(tmp_mac)
		dev_ip.append(tmp_ip)
		dev_label.append(tmp_label)
		dev_guest.append(tmp_guest)
		dev_fw_version.append(tmp_fw_version)
		dev_hw_version.append(tmp_hw_version)

#		tmp_current_fw = 0
		try:
		    content, response_code = fetch_thing("https://www.swiitch.ru/pub/firmware/swiitch_" + tmp_mac + ".txt",'GET')
		except:
		    try:
			content, response_code = fetch_thing("http://www.swiitch.ru/pub/firmware/swiitch_" + tmp_mac + ".txt",'GET')
		    except:
			content = ""
		try:
			tmp_current_fw = float(content)
		except:
			tmp_current_fw = Current_FW

		dev_fw_version_cur.append(tmp_current_fw)

		print(str(len(dev_mac)) + " ip: " + tmp_ip + ", mac: " + tmp_mac + ", fw: " + str(tmp_fw_version) + ", update: " + bool_to_string_yes(tmp_current_fw > tmp_fw_version) + ", hw: " + tmp_hw_version)
		ADD_New_Device_Line(len(dev_mac)-1)


	else:
		dev_mac[found_device_num] = tmp_mac
		dev_ip[found_device_num] = tmp_ip
		dev_label[found_device_num] = tmp_label
		dev_guest[found_device_num] = tmp_guest
		dev_fw_version[found_device_num] = tmp_fw_version
		dev_hw_version[found_device_num] = tmp_hw_version
		UPDATE_Device_Line(found_device_num)
#		print("FOUND OLD DEVICE : ip: " + tmp_ip + " " + tmp_mac + " " + tmp_label + " is guest: " + bool_to_string( tmp_guest) + " " + tmp_version)


def ADD_New_Device_Line(hw_channel):
#	print("Add device : " + str(hw_channel) + " label: " + dev_label[hw_channel])
	SCAN_Button["text"] = TEXT_String_Device_Found + str(len(dev_mac))


	Label_DEV_Num.append(Label(frame, text=str(hw_channel+1), width=2))
	Label_DEV_Num[hw_channel].grid(row=hw_channel, column=0)

	Label_DEV_Label.append(Label(frame, text=dev_label[hw_channel], width=15))
	Label_DEV_Label[hw_channel].grid(row=hw_channel, column=1)

	Label_DEV_IP.append(Label(frame, text=dev_ip[hw_channel], width=12))
	Label_DEV_IP[hw_channel].grid(row=hw_channel, column=2)

	Label_DEV_MAC.append(Label(frame, text=dev_mac[hw_channel], width=12))
	Label_DEV_MAC[hw_channel].grid(row=hw_channel, column=3)

	Label_DEV_HW_Version.append(Label(frame, text=dev_hw_version[hw_channel], width=13))
	Label_DEV_HW_Version[hw_channel].grid(row=hw_channel, column=4)

	Label_DEV_FW_Version.append(Label(frame, text=dev_fw_version[hw_channel], width=6))
	Label_DEV_FW_Version[hw_channel].grid(row=hw_channel, column=5)

	Label_DEV_FW_Version_Cur.append(Label(frame, text=dev_fw_version_cur[hw_channel], width=6))
	Label_DEV_FW_Version_Cur[hw_channel].grid(row=hw_channel, column=6)


	Button_DEV_Update_File.append(Button(frame, text=TEXT_String_From_File, width=8, command = lambda: HW_DEV_Update_File(hw_channel)))
	Button_DEV_Update_File[hw_channel].grid(row=hw_channel, column=7)

	Button_DEV_Update.append(Button(frame, text=TEXT_String_From_Cloud, width=8, command = lambda: HW_DEV_Update(hw_channel)))

	if dev_fw_version_cur[hw_channel] > dev_fw_version[hw_channel] :
		Button_DEV_Update[hw_channel].grid(row=hw_channel, column=8)

	canvas.create_window(0, 0, anchor=NW, window=frame)
	frame.update_idletasks()
	canvas.config(scrollregion=canvas.bbox("all"))


def UPDATE_Device_Line(hw_channel):
#	print("Update device : " + str(hw_channel) + " label: " + dev_label[hw_channel])
	Label_DEV_Label[hw_channel]["text"] = dev_label[hw_channel]
	Label_DEV_IP[hw_channel]["text"] = dev_ip[hw_channel]
	Label_DEV_MAC[hw_channel]["text"] = dev_mac[hw_channel]
	Label_DEV_HW_Version[hw_channel]["text"] = dev_hw_version[hw_channel]
	Label_DEV_FW_Version[hw_channel]["text"] = dev_fw_version[hw_channel]






def HW_DEV_Update(hw_channel):
	global HW_BLOCK_UPDATE
	if HW_BLOCK_UPDATE:
		print("Already update proccess")
#		return

	result = mbox.askyesno(TEXT_String_Update_Firmware, TEXT_String_Sure_Update_Firmware + " " + dev_label[hw_channel] + " (" + dev_mac[hw_channel] + ") ?")
	if result == False :
		return


	download_ok = download_file("http://www.swiitch.ru/pub/firmware/swiitch_" + dev_mac[hw_channel] + ".bin", "swiitch_" + dev_mac[hw_channel] + ".bin")
	if download_ok:
		print("ok")
	else:
		mbox.showwarning(TEXT_String_Error, TEXT_String_Error_download_firmware_for + " " + dev_label[hw_channel] + " (" + dev_mac[hw_channel] + ") !")
		return

	print("START UPDATE DEVICE: " + str(hw_channel) + " label: " + dev_label[hw_channel])
	HW_BLOCK_UPDATE = 1

#    fw_filename = "swiitch_.bin"
#    fw_ip = dev_ip[hw_channel]
#    fw_ip = "192.168.1.25"


	Firmware_Flash(dev_ip[hw_channel], "0.0.0.0", "8266", random.randint(10000,60000), "", "swiitch_" + dev_mac[hw_channel] + ".bin", FLASH)
#    root.after(1000, UIUpdate)





def UPDATE_CURRENT_FW():
	global Current_FW
	try:
	    content, response_code = fetch_thing("https://www.swiitch.ru/pub/firmware/version",'GET')
	except:
	    content, response_code = fetch_thing("http://www.swiitch.ru/pub/firmware/version",'GET')
#	print("http return " + str(content))
	try:
		tmp_current_fw = float(content)
	except:
		tmp_current_fw = 0

#	print("real cur fw: " + str(Current_FW) + " get: " + str(tmp_current_fw))
	if tmp_current_fw > 0:
		Current_FW = tmp_current_fw


def HW_DEV_Update_File(hw_channel):
	print("Open dialog")
#    ftypes = [('Python files', '*.py'), ('All files', '*')]
	dlg = filedialog.Open(root, initialdir = "./")
	filename = dlg.show()
	if filename == '':
		return

	tmp_filename = str(filename)

#	result = mbox.askyesno(TEXT_String_Update_Firmware, TEXT_String_Sure_Update_Firmware + " " + dev_label[hw_channel] + " (" + dev_mac[hw_channel] + ") " + TEXT_String_From_file + " " + filename)
#	result = mbox.askyesno("UP", "XXX " + dev_label[hw_channel] + " (" + dev_mac[hw_channel] + ") " + "xxx " + filename)
	result = mbox.askyesno(TEXT_String_Update_Firmware, TEXT_String_Sure_Update_Firmware + " " + dev_label[hw_channel] +" (" + dev_mac[hw_channel] + ") " + TEXT_String_From_file + " " + tmp_filename + " ?")
	if result == False:
		return

	print("START UPDATE DEVICE: " + str(hw_channel) + " label: " + dev_label[hw_channel] + " with ip: " + dev_ip[hw_channel])
	Firmware_Flash(dev_ip[hw_channel], "0.0.0.0", "8266", random.randint(10000,60000), "", filename, FLASH)






def get_param_value_full(full_str, str_begin_char, str_end_char):
	tmp_str = ""
	str_begin = full_str.find(str_begin_char)
	tmp_str = full_str[str_begin:]
	str_end = tmp_str.find(str_end_char)
	tmp_str = tmp_str[len(str_begin_char):str_end]
	return tmp_str

def get_param_value(full_str, str_begin_char):
	return get_param_value_full(full_str, str_begin_char, "##")


def bool_to_string(value):
	if value :
		return "on"
	else:
		return "off"

def bool_to_string_yes(value):
	if value :
		return "yes"
	else:
		return "no"

def UIUpdate():
#	print("UIUpdate work")
	UDP_Listen()
	root.after(100, UIUpdate)





def main():
#	print("Begin init UI")
	windowWidth = root.winfo_reqwidth()
	windowHeight = root.winfo_reqheight()
	positionRight = int(root.winfo_screenwidth()/2 - app_width/2)
	positionDown = int(root.winfo_screenheight()/2 - app_height/2)


	root.geometry("{}x{}+{}+{}".format(app_width, app_height, positionRight, positionDown))
	root.lift()
	root.lift()
	root.call('wm', 'attributes', '.', '-topmost', True)
	root.after_idle(root.call, 'wm', 'attributes', '.', '-topmost', False)

	root.title(TEXT_String_APP_Label)
	root.after(1000, UIUpdate)
	root.mainloop()

if __name__ == '__main__':
	UDP_Init()
#	UPDATE_CURRENT_FW()
	main()
