'''VMSS Zones - tool to visualize multi-zone scale sets'''

import json
import os
import sys
import threading
import tkinter as tk
from time import sleep, strftime
from tkinter import messagebox

import subscription
import vmssz

# size and color defaults
btnwidth = 14
entrywidth = 15
if os.name == 'posix':  # Mac OS
    geometry1 = '700x140'
    geometry100 = '700x450'
    geometry_wide = '1500x450'
    list_width = 14
    status_width = 98
    canvas_width100 = 690
    canvas_width1000 = 1300
else:
    geometry1 = '540x128'
    geometry100 = '540x410'
    geometry_wide = '1230x410'
    list_width = 8
    status_width = 67
    canvas_width100 = 520
    canvas_width1000 = 1230

canvas_height100 = 195
canvas_height1000 = 700
frame_bgcolor = '#B0E0E6'
canvas_bgcolor = '#F0FFFF'
btncolor = '#F8F8FF'

# Load Azure app defaults
try:
    with open('vmssconfig.json') as configFile:
        config_data = json.load(configFile)
except FileNotFoundError:
    sys.exit('Error: Expecting vmssconfig.json in current folder')

sub = subscription.subscription(config_data['tenantId'], config_data['appId'],
                                config_data['appSecret'], config_data['subscriptionId'])
current_vmss = None
refresh_thread_running = False

def subidkeepalive():
    '''thread to keep access token alive'''
    while True:
        sleep(2000)
        sub.auth()
        current_vmss.update_token(sub.access_token)

def refresh_loop():
    '''thread to refresh details until provisioning is complete'''
    global refresh_thread_running
    sleep_time = 5
    while True:
        while refresh_thread_running is True:
            current_vmss.refresh_model()
            # for demoing small scale sets - dont' switch off refresh
            # if current_vmss.status == 'Succeeded' or current_vmss.status == 'Failed':
            if current_vmss.status == 'Failed':
                refresh_thread_running = False
            sleep(sleep_time)
            vmssdetails()
        sleep(sleep_time)


# start timer thread
timer_thread = threading.Thread(target=subidkeepalive, args=())
timer_thread.daemon = True
timer_thread.start()

# start refresh thread
refresh_thread = threading.Thread(target=refresh_loop, args=())
refresh_thread.daemon = True
refresh_thread.start()


def assign_color_to_power_state(powerstate):
    '''visually represent VM powerstate with a color'''
    if powerstate == 'running':
        return 'green'
    elif powerstate == 'stopped':
        return 'red'
    elif powerstate == 'starting':
        return 'yellow'
    elif powerstate == 'stopping':
        return 'orange'
    elif powerstate == 'deallocating':
        return 'grey'
    elif powerstate == 'deallocated':
        return 'black'
    else: # unknown
        return 'blue'

def draw_grid(originx, originy, row_height, ystart, zone):
    '''draw a grid to delineate zones and fault domains on the VMSS heatmap'''
    vmcanvas.create_text(originx + 180, originy + 10, text='Zone: ' + str(zone))

    # horizontal lines for FDs
    for y in range(5):
        ydelta = y * row_height
        vmcanvas.create_text(originx + 20, originy + ydelta + 50, text='FD ' + str(y))
        if y < 4:
            vmcanvas.create_line(originx + 35, originy + ystart + ydelta, originx + 390, \
                originy + ystart + ydelta)


def draw_vms():
    '''draw a heat map for the VMSS VMs'''
    xval = 55
    yval = 40
    diameter = 10
    row_height = 27
    ystart = 60
    originx = 0
    originy = 0

    vmcanvas.delete("all")
    # one rectangle for each zone
    vmcanvas.create_rectangle(0, 0, canvas_width1000/3, canvas_height100,
                              outline="#FFFACD", fill="#FFFACD")
    vmcanvas.create_rectangle(canvas_width1000/3, 0, 2*canvas_width1000/3, canvas_height100,
                              outline="#C1FFC1", fill="#C1FFC1")
    vmcanvas.create_rectangle(2*canvas_width1000/3, 0, canvas_width1000, canvas_height100,
                              outline="#fd0", fill="#fd0")
    # draw 3 'zones' on canvas
    fontsize = 5
    for zone in current_vmss.zones:
        draw_grid(originx, originy, row_height, ystart, zone['zone'])
        for zfd in zone['fds']:
            fd_id = zfd['fd']
            xinc = xval
            for vm_info in zfd['vms']:
                instance_id = vm_info['vmid']
                powerstate = vm_info['power_state']
                statuscolor = assign_color_to_power_state(powerstate)
                ydelta = int(fd_id) * 26
                # colored circle represents machine power state
                vmcanvas.create_oval(originx + xinc, originy + yval + ydelta,
                                     originx + xinc + diameter,
                                     originy + yval + ydelta + diameter, fill=statuscolor)
                # print VM ID under each circle
                vmcanvas.create_text(originx + xinc + 7, originy + yval + ydelta + 15,
                                     font=("Purisa", fontsize), text=instance_id)
                xinc += 20

        originx += canvas_width1000/3
    vmcanvas.update_idletasks() # refresh the display
    sleep(0.01) # add a little nap seems to make the display refresh more reliable

def getzones():
    '''build a list of vm ids by zone'''
    zone_num = int(selectedz.get())
    zinstancelist = []
    # loop through fds
    for fdomain in current_vmss.zones[int(zone_num)-1]['fds']:
        for vm_info in fdomain['vms']:
            zinstancelist.append(vm_info['vmid'])
    # build list of VM IDs in the zone
    return zinstancelist


def startz():
    '''start all the VMs in a fault domain'''
    global refresh_thread_running
    zinstancelist = getzones()
    current_vmss.startvm(json.dumps(zinstancelist))
    statusmsg(current_vmss.status)
    refresh_thread_running = True


def powerz():
    '''power off all the VMs in a fault domain'''
    global refresh_thread_running
    zinstancelist = getzones()
    current_vmss.poweroffvm(json.dumps(zinstancelist))
    statusmsg(current_vmss.status)
    refresh_thread_running = True


def reimagez():
    '''reimage all the VMs in a fault domain'''
    global refresh_thread_running
    zinstancelist = getzones()
    current_vmss.reimagevm(json.dumps(zinstancelist))
    statusmsg(current_vmss.status)
    refresh_thread_running = True


def upgradez():
    '''upgrade all the VMs in a fault domain'''
    global refresh_thread_running
    zinstancelist = getzones()
    current_vmss.upgradevm(json.dumps(zinstancelist))
    statusmsg(current_vmss.status)
    refresh_thread_running = True


def reimagevm():
    '''reimage a VM or list of VMs'''
    global refresh_thread_running
    vmid = vmtext.get()
    vmstring = '["' + vmid + '"]'
    current_vmss.reimagevm(vmstring)
    statusmsg(current_vmss.status)
    refresh_thread_running = True


def upgradevm():
    '''upgrade a VM or list of VMs'''
    global refresh_thread_running
    vmid = vmtext.get()
    vmstring = '["' + vmid + '"]'