from flask import session, abort, copy_current_request_context

from .eeg_python_utils import utils, errors, time_utils

import time
import datetime
import os
from subprocess import call, check_call, check_output, CalledProcessError
from multiprocessing import Process, ProcessError

import threading
import re
import math
import json


###############################################################################
# UPGRADE

#  Requires
#  ** /home/eeg/do_update (lcd_driver SVN
#  Must add to visudo:
#  ** -/home/eeg/do_update

ERROR = 'error'
SAVING = 'saving'
UPLOADING = 'uploading'
UPLOADED = 'uploaded'
UPGRADING = 'upgrading'
SUCCESS = 'success'

UPGRADE_STAGES = [
    SAVING,
    UPLOADING,
    UPLOADED,
    UPGRADING,
    SUCCESS,
]

UPGRADE_STATUS_PATH = os.environ['UPGRADE_STATUS_PATH']
UPGRADE_PROGRESS_PATH = os.environ['UPGRADE_PROGRESS_PATH']

STATUS_MESSAGES = {
    'saving': 'Saving upgrade file...',
    'success': 'Upgrade completed successfully.',
    'successreboot': 'Upgrade completed successfully. The system will restart to apply changes.',
    'xilinx': 'Upgrade in progress...',
    'updating': 'Upgrade in progress...',
    'upgrading': 'Upgrade in progress...',
    'uploaded': 'File has been uploaded. Starting upgrade...',
    'uploading': 'File is uploading...',
    'invalid': 'Upgrade failed because the uploaded file was not a proper upgrade.',
    'invalid_file': 'Invalid update file. Please check the file you selected to upload and then try again.',
    'move_failed': 'Move failed. This may have been caused by incorrect permissions on the device. Please contact support.',
    'upload_error': 'There was an error uploading the upgrade file.',
    'idle': 'Idle',
    'build error':'The update file is outdated.',
    'product error':'The update file is not intended for use with Lexi Local.'
}

ERROR_STATUSES = [
    'invalid',
    'invalid_file',
    'move_failed',
    'upload_error',
    'build error',
    'product error'
]

def set_upgrade_progress(progress_status, error_message=None):
    if error_message == None:
        if progress_status in ERROR_STATUSES:
            error_message = STATUS_MESSAGES[progress_status]
        elif not STATUS_MESSAGES.get(progress_status):
            # If the progress_status does not match ANY status, we consider it an error and send
            # a generic message.
            error_message = 'There was an internal error while attempting upgrade.'
    min_steps = len(UPGRADE_STAGES)
    current_progress = get_upgrade_progress()
    print('current upgrade progress: {}'.format(utils.jformat(current_progress)))
    current_status = current_progress.get('status')
    current_step = current_progress.get('step', 0)
    current_total_steps = current_progress.get('total_steps', min_steps)
    current_extra_steps = current_progress.get('extra_steps', 0)
    current_step_history = current_progress.get('step_history', [])
    # print('current_progress', current_progress)
    # print('current_status', current_status)
    # print('current_step', current_step)
    # print('current_total_steps', current_total_steps)
    # print('current_extra_steps', current_extra_steps)
    original_status = progress_status
    if 'success' in progress_status:
        progress_status = SUCCESS
    try:
        process_index = UPGRADE_STAGES.index(progress_status)
        process_step = process_index + 1 + current_extra_steps
    except ValueError:
        process_step = current_step + 1
        current_extra_steps += 1
        current_total_steps = min_steps + current_extra_steps

    process_fraction = float(process_step) / float(current_total_steps)
    progress_percent = math.ceil(process_fraction * 100)
    current_step_history.append('{}: {}'.format(str(process_step), progress_status))
    upgrade_progress = {
        'status': progress_status,
        'status_message': STATUS_MESSAGES.get(original_status), 
        'progress': progress_percent,
        'step': process_step,
        'extra_steps': current_extra_steps,
        'step_history': current_step_history,
        'total_steps': current_total_steps,
    }

    # print('new current_progress', current_progress)
    # print('new current_status', current_status)
    # print('new current_step', current_step)
    # print('new current_total_steps', current_total_steps)
    # print('new current_extra_steps', current_extra_steps)
    print('new upgrade progress: {}'.format(utils.jformat(upgrade_progress)))

    if error_message is not None:
        upgrade_progress['error_message'] = error_message
        upgrade_progress['error'] = True
        upgrade_progress['progress'] = 0

    tmp_path = utils.get_tmp_path(UPGRADE_PROGRESS_PATH)
    with open(tmp_path, 'w') as f:
        json.dump(upgrade_progress, f)
    utils.move_file(tmp_path, UPGRADE_PROGRESS_PATH)


def get_upgrade_progress():
    if not utils.check_if_file(UPGRADE_PROGRESS_PATH):
        return {}
    with open(UPGRADE_PROGRESS_PATH, 'r') as f:
        upgrade_progress = json.load(f)
        # print('getting upgrade progress: {}'.format(utils.jformat(upgrade_progress)))
        return upgrade_progress


def clear_upgrade_progress():
    print('clearing upgrade progress')
    # utils.tb()
    utils.remove_file(UPGRADE_PROGRESS_PATH)


def set_upgrade_status(upgrade_status):
    print('get upgrade status: {}'.format(upgrade_status))
    with open(UPGRADE_STATUS_PATH, 'w') as status_file:
        status_file.write(str(upgrade_status))


def get_upgrade_status():
    status = ''
    last_modified = -1
    if os.path.isfile(UPGRADE_STATUS_PATH):
        with open(UPGRADE_STATUS_PATH, 'r') as status_file:
            status = status_file.read()
            status = status.strip()
        last_modified = os.path.getmtime(UPGRADE_STATUS_PATH)
    else:
        status = 'idle'
        last_modified = time_utils.now_timestamp()

    msg = STATUS_MESSAGES.get(status, 'Upgrade failed for the following reason: {}'.format(status))
    upgrade_status = {
        'status': status,
        'msg': msg,
        'last_modified': last_modified
    }
    # print(utils.jformat(upgrade_status))
    return upgrade_status


def watch_upgrade_status():
    status = get_upgrade_status().get('status')
    while status not in ['idle', None]:
        time.sleep(0.05)
        status_check = get_upgrade_status().get('status')
        if status != status_check:
            print(status, status_check)
            status = status_check
            set_upgrade_progress(status)


def start_upgrade(upgrade_file):
    print('starting upgrade')
    upgrade_status = get_upgrade_status().get('status')
    print(upgrade_status)
    print(upgrade_file)
    is_uploaded = upgrade_status == UPLOADED
    upgrade_is_file = os.path.isfile(upgrade_file)
    if is_uploaded and upgrade_is_file:
        set_upgrade_status(UPGRADING)
        # session['upgrade_status'] = upgrades.UPGRADING
        full_status = get_upgrade_status()
        print('upgrading: {}'.format(utils.jformat(full_status)))
        log_path = '/tmp/upgrade.log'
        start_time = datetime.datetime.now()
        try:
            # print('doing update')
            upgrade_call = 'sudo /home/eeg/do_update ok \'{}\''.format(upgrade_file)
            output = check_output(upgrade_call, shell=True)
            print('did update')
            # print('output: {}'.format(output))
        except CalledProcessError as e:
            errors.bad_request(e.output)

        end_time = datetime.datetime.now()
        with open(log_path, 'w') as log:
            log.write('Update began at {}\n'.format(start_time))
            print(output)
            for line in output.splitlines():
                # print('line: {}'.format(line))
                log.write('{}\n'.format(line))
            log.write('Update completed at {}\n'.format(end_time))


def upload_upgrade_file(tmp_name):
    print('starting upload')
    set_upgrade_status(UPLOADING)
    error = False
    if os.path.isfile(tmp_name):
        print('path is file')
        # Check to see if the file uploaded is a valid eeg_update file
        try:
            check_tar_contents = 'tar -jtf \'{}\' .eeg_update ;'.format(tmp_name)
            print(check_tar_contents)
            check_call(check_tar_contents, shell=True)
        except CalledProcessError as e:
            # file is invalid, return to page and ask for new file
            error = 'invalid_file'
            print(str(error))

        if not error:
            # get title, path, etc
            output = check_output('tar -jxOf \'{}\' .eeg_update/TITLE'.format(tmp_name), shell=True)
            print(output)

    else:
        error = 'upload_error'
        print(error)

    if not error:
        set_upgrade_status(UPLOADED)
        print('starting upgrade')
        full_status = utils.jformat(get_upgrade_status())
        print('upgrade status:\n{}\n'.format(full_status))
        upgrade_process = Process(target=start_upgrade, args=(tmp_name,))
        upgrade_process.start()
    else:

        errors.bad_request(UPGRADE_ERROR_MESSAGES.get(error, error))

                                                                  # ^^UPGRADE^^
###############################################################################
