#!/usr/bin/env python
import os
import os.path
import sys
import shutil
from subprocess import call
import importlib
import unittest
import re

from testcases import settings


class Workspace(object):

    def __init__(self):
        self.root_dir = os.getcwd()
        self.build_dir = os.path.join(self.root_dir, "tmpbin")
        self.log_dir = os.path.join(self.root_dir, "logs")
        self.tests_dir = os.path.join(self.root_dir, "testcases")
        self.examples_dir = os.path.join(self.root_dir, "../PubSubClient/examples")
        self.examples = []
        self.tests = []
        if not os.path.isdir("../src"):
            raise Exception("Cannot find PubSubClient library")
        try:
            return __import__('ino')
        except ImportError:
            raise Exception("ino tool not installed")

    def init(self):
        if os.path.isdir(self.build_dir):
            shutil.rmtree(self.build_dir)
        os.mkdir(self.build_dir)
        if os.path.isdir(self.log_dir):
            shutil.rmtree(self.log_dir)
        os.mkdir(self.log_dir)

        os.chdir(self.build_dir)
        call(["ino", "init"])

        shutil.copytree("../../PubSubClient", "lib/PubSubClient")

        filenames = []
        for root, dirs, files in os.walk(self.examples_dir):
            filenames += [os.path.join(root, f) for f in files if f.endswith(".ino")]
        filenames.sort()
        for e in filenames:
            self.examples.append(Sketch(self, e))

        filenames = []
        for root, dirs, files in os.walk(self.tests_dir):
            filenames += [os.path.join(root, f) for f in files if f.endswith(".ino")]
        filenames.sort()
        for e in filenames:
            self.tests.append(Sketch(self, e))

    def clean(self):
        shutil.rmtree(self.build_dir)


class Sketch(object):
    def __init__(self, wksp, fn):
        self.w = wksp
        self.filename = fn
        self.basename = os.path.basename(self.filename)
        self.build_log = os.path.join(self.w.log_dir, "%s.log" % (os.path.basename(self.filename),))
        self.build_err_log = os.path.join(self.w.log_dir, "%s.err.log" % (os.path.basename(self.filename),))
        self.build_upload_log = os.path.join(self.w.log_dir, "%s.upload.log" % (os.path.basename(self.filename),))

    def build(self):
        sys.stdout.write(" Build:   ")
        sys.stdout.flush()

        # Copy sketch over, replacing IP addresses as necessary
        fin = open(self.filename, "r")
        lines = fin.readlines()
        fin.close()
        fout = open(os.path.join(self.w.build_dir, "src", "sketch.ino"), "w")
        for l in lines:
            if re.match(r"^byte server\[\] = {", l):
                fout.write("byte server[] = { %s };\n" % (settings.server_ip.replace(".", ", "),))
            elif re.match(r"^byte ip\[\] = {", l):
                fout.write("byte ip[] = { %s };\n" % (settings.arduino_ip.replace(".", ", "),))
            else:
                fout.write(l)
        fout.flush()
        fout.close()

        # Run build
        fout = open(self.build_log, "w")
        ferr = open(self.build_err_log, "w")
        rc = call(["ino", "build"], stdout=fout, stderr=ferr)
        fout.close()
        ferr.close()
        if rc == 0:
            sys.stdout.write("pass")
            sys.stdout.write("\n")
            return True
        else:
            sys.stdout.write("fail")
            sys.stdout.write("\n")
            with open(self.build_err_log) as f:
                for line in f:
                    print(" " + line)
            return False

    def upload(self):
        sys.stdout.write(" Upload:  ")
        sys.stdout.flush()
        fout = open(self.build_upload_log, "w")
        rc = call(["ino", "upload"], stdout=fout, stderr=fout)
        fout.close()
        if rc == 0:
            sys.stdout.write("pass")
            sys.stdout.write("\n")
            return True
        else:
            sys.stdout.write("fail")
            sys.stdout.write("\n")
            with open(self.build_upload_log) as f:
                for line in f:
                    print(" " + line)
            return False

    def test(self):
        # import the matching test case, if it exists
        try:
            basename = os.path.basename(self.filename)[:-4]
            i = importlib.import_module("testcases." + basename)
        except:
            sys.stdout.write(" Test:    no tests found")
            sys.stdout.write("\n")
            return
        c = getattr(i, basename)

        testmethods = [m for m in dir(c) if m.startswith("test_")]
        testmethods.sort()
        tests = []
        for m in testmethods:
            tests.append(c(m))

        result = unittest.TestResult()
        c.setUpClass()
        if self.upload():
            sys.stdout.write(" Test:    ")
            sys.stdout.flush()
            for t in tests:
                t.run(result)
                print(str(result.testsRun - len(result.failures) - len(result.errors)) + "/" + str(result.testsRun))
                if not result.wasSuccessful():
                    if len(result.failures) > 0:
                        for f in result.failures:
                            print("-- " + str(f[0]))
                            print(f[1])
                    if len(result.errors) > 0:
                        print(" Errors:")
                        for f in result.errors:
                            print("-- " + str(f[0]))
                            print(f[1])
        c.tearDownClass()


if __name__ == '__main__':
    run_tests = True

    w = Workspace()
    w.init()

    for e in w.examples:
        print("--------------------------------------")
        print("[" + e.basename + "]")
        if e.build() and run_tests:
            e.test()
    for e in w.tests:
        print("--------------------------------------")
        print("[" + e.basename + "]")
        if e.build() and run_tests:
            e.test()

    w.clean()
