#!/usr/bin/env python
#
# Copyright (c) 2016, Pycom Limited.
#
# This software is licensed under the GNU GPL version 3 or any
# later version, with permitted additional terms. For more information
# see the Pycom Licence v1.0 document supplied with this file, or
# available at https://www.pycom.io/opensource/licensing
#

"""
Flash the ESP32 (bootloader, partitions table and factory app).

How to call esptool:

python esptool.py '--chip', 'esp32', '--port', /dev/ttyUSB0, '--baud', '921600', 'write_flash', '-z', '--flash_mode', 'dio', '--flash_freq', '40m', '--flash_size', 'detect', '0x1000', bootloader.bin, '0x8000', partitions.bin, '0x10000', application.bin, '0x3FF000', 'config_no_wifi.bin'

"""

from esptool import ESP32ROM
import os
import sys
import struct
import sqlite3
import argparse
import subprocess
import threading
import time
import fw_version
import csv

working_threads = {}
macs_db = None
wmacs = {}

DB_MAC_UNUSED = 0
DB_MAC_ERROR = -1
DB_MAC_LOCK = -2
DB_MAC_OK = 1

def open_macs_db(db_filename):
    global macs_db
    if not os.path.exists(db_filename):
        print("MAC addresses database not found")
        sys.exit(1)
    macs_db = sqlite3.connect(db_filename)

def fetch_MACs(number):
    return [x[0].encode('ascii', 'ignore') for x in macs_db.execute("select mac from macs where status = 0 order by rowid asc limit ?", (number,)).fetchall()]

def set_mac_status(mac, wmac, status):
    macs_db.execute("update macs set status = ?, last_touch = strftime('%s','now'), wmac = ? where mac = ?", (status, wmac, mac))
    macs_db.commit()

def print_exception(e):
    print ('Exception: {}, on line {}'.format(e, sys.exc_info()[-1].tb_lineno))

def erase_flash(port, command):
    global working_threads
    process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
    num_erases = 0

    # poll the process for new output until finished
    while True:
        nextline = process.stdout.readline()
        if nextline == '' and process.poll() != None:
            break
        if 'Chip erase completed successfully' in nextline:
            sys.stdout.write('Board erased OK on port %s\n' % port)
            num_erases += 1
        sys.stdout.flush()

    # hack to give feedback to the main thread
    if process.returncode != 0 or num_erases != 1:
        working_threads[port] = None

def read_wlan_mac(port, command):
    global working_threads
    global wmacs
    process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)

    mac_read = False

    # poll the process for new output until finished
    while True:
        nextline = process.stdout.readline()
        if nextline == '' and process.poll() != None:
            break
        if 'MAC: ' in nextline:
            wmacs[port] = nextline[5:-1].replace(":", "-").upper()
            sys.stdout.write('MAC address %s read OK on port %s\n' % (nextline[5:-1], port))
            mac_read = True
        sys.stdout.flush()

    # hack to give feedback to the main thread
    if process.returncode != 0 or not mac_read:
        working_threads[port] = None


def set_vdd_sdio_voltage(port, command):
    global working_threads
    process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)

    # poll the process for new output until finished
    while True:
        nextline = process.stdout.readline()
        if nextline == '' and process.poll() != None:
            break
        if 'VDD_SDIO setting complete' in nextline:
            sys.stdout.write('Board VDD_SDIO Voltage configured OK on port %s\n' % port)
        sys.stdout.flush()

    # hack to give feedback to the main thread
    if process.returncode != 0:
        working_threads[port] = None

def flash_firmware(port, command):
    global working_threads
    process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
    num_hashes = 0

    # poll the process for new output until finished
    while True:
        nextline = process.stdout.readline()
        if nextline == '' and process.poll() != None:
            break
        if 'at 0x00001000' in nextline:
            sys.stdout.write('Bootloader programmed OK on port %s\n' % port)
        elif 'at 0x00008000' in nextline:
            sys.stdout.write('Partition table programmed OK on port %s\n' % port)
        elif 'at 0x00010000' in nextline:
            sys.stdout.write('Application programmed OK on port %s\n' % port)
        elif 'Hash of data verified' in nextline:
            num_hashes += 1
        sys.stdout.flush()

    # hack to give feedback to the main thread
    if process.returncode != 0 or num_hashes != 3:
        working_threads[port] = None

def run_initial_test(port, board):
    global working_threads

    if board == 'LoPy':
        import run_initial_lopy_test as run_test
    elif board == 'SiPy':
        import run_initial_sipy_test as run_test
    else:
        import run_initial_wipy_test as run_test

    try:
        if not run_test.test_board(port):
            # same trick to give feedback to the main thread
            working_threads[port] = None
    except Exception: