#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# This file is part of INGInious. See the LICENSE and the COPYRIGHTS files for
# more information about the licensing of this file.

import difflib
import argparse
import glob
import os
import sys
import time

# If INGInious files are not installed in Python path
sys.path.append(os.path.join(os.path.abspath(os.path.dirname(os.path.realpath(__file__))),'..','..'))

import inginious.common.tasks
from inginious.common.base import load_json_or_yaml
from inginious.common.course_factory import create_factories
from inginious.frontend.common.parsable_text import ParsableText
from inginious.frontend.common.arch_helper import create_arch, start_asyncio_and_zmq
from inginious.client.client_sync import ClientSync


def job_done_callback(result, filename, inputfiles, data):
    print('\x1b[34;1m[' + str(job_done_callback.jobs_done + 1) + '/' + str(len(inputfiles)) + ']' + " Testing input file : " + filename + '\033[0m')

    parse_text(task, result)

    # Print stdout if verbose
    if verbose:
        print('\x1b[1m-> Complete standard output : \033[0m')
        for line in result['stdout'].splitlines(1):
            print('\t' + line.strip('\n'))

    # Start the comparison
    noprob = True

    if 'stderr' in result and result['stderr']:
        noprob = False
        print('\x1b[31;1m-> There was some error(s) during execution : \033[0m')
        for line in result['stderr'].splitlines(1):
            print('\x1b[31;1m\t' + line.strip('\n') + '\033[0m')

    if 'stdout' in data and data['stdout']:
        if data['stdout'] != result['stdout']:
            noprob = False
            print("\033[1m-> Standard output doesn't match :\033[0m")
            for line in difflib.unified_diff(data['stdout'].splitlines(1), result['stdout'].splitlines(1), fromfile='Expected', tofile='Actual'):
                print('\t' + line.strip('\n'))

    if 'result' in data and data['result']:
        if data['result'] != result['result'][0]:
            noprob = False
            print("\033[1m-> Result doesn't match :\033[0m")
            print("\t Expected result : " + data['result'])
            print("\t Actual result : " + result['result'][0])

    if 'text' in data and data['text']:
        if not result['result'][1]:
            noprob = False
            print("\033[1m-> No global feedback given \033[0m")
            print("\t Expected result : " + data['text'])
        elif data['text'].strip() != result['result'][1].strip():
            noprob = False
            print("\033[1m-> Global feedback doesn't match :\033[0m")
            print("\t Expected result : " + data['text'])
            print("\t Actual result : " + result['result'][1])

    if 'problems' in data and data['problems']:
        if not 'problems' in result:
            noprob = False
            print("\033[1m-> No specific problem feedback given as expected \033[0m")
        else:
            for problem in data['problems']:
                if not problem in result['problems']:
                    noprob = False
                    print("\033[1m-> No feedback for problem id " + problem + " given \033[0m")
                    print("\t Expected result : " + data['problems'][problem][0] + "\n\t" + data['problems'][problem][1])
                elif data['problems'][problem][0].strip() != result['problems'][problem][0].strip():
                    noprob = False
                    print("\033[1m-> Result for problem id " + problem + " doesn't match :\033[0m")
                    print("\t Expected result : " + data['problems'][problem][0])
                    print("\t Actual result : " + result['problems'][problem][0])
                elif data['problems'][problem][1].strip() != result['problems'][problem][1].strip():
                    noprob = False
                    print("\033[1m-> Feedback for problem id " + problem + " doesn't match :\033[0m")
                    print("\t Expected result : " + data['problems'][problem][1])
                    print("\t Actual result : " + result['problems'][problem][1])

    if 'tests' in data and data['tests']:
        if not 'tests' in result:
            noprob = False
            print("\033[1m-> No tests results given as expected \033[0m")
        else:
            for tag in data['tests']:
                if not tag in result['tests']:
                    noprob = False
                    print("\033[1m-> No test result with tag '" + tag + "' given \033[0m")
                    print("\t Expected result : " + data['tests'][tag])
                elif data['tests'][tag] != result['tests'][tag]:
                    noprob = False
                    print("\033[1m-> Test with tag '" + tag + "' failed :\033[0m")
                    print("\t Expected result : " + data['tests'][tag])
                    print("\t Actual result : " + result['tests'][tag])

    if noprob:
        print("\033[32;1m-> All tests passed \033[0m")

    job_done_callback.jobs_done += 1

job_done_callback.jobs_done = 0


def get_config(configfile):
    if not configfile:
        if os.path.isfile("./configuration.yaml"):
            configfile = "./configuration.yaml"
        elif os.path.isfile("./configuration.json"):
            configfile = "./configuration.json"
        else:
            raise Exception("No configuration file found")

    return load_json_or_yaml(configfile)


def launch_job(filename, data, inputfiles):
    result, grade, problems, tests, custom, archive, stdout, stderr = job_manager.new_job(task, data["input"], "Task tester", True)
    job_done_callback({"result":result, "grade": grade, "problems": problems, "tests": tests, "custom": custom, "archive": archive, "stdout": stdout, "stderr": stderr}, filename, inputfiles, data)


def parse_text(task, job_result):
    if "text" in job_result:
        job_result["text"] = ParsableText(job_result["text"], task.get_response_type()).parse()
    if "problems" in job_result:
        for problem in job_result["problems"]:
            job_result["problems"][problem] = ParsableText(job_result["problems"][problem], task.get_response_type()).parse()

if __name__ == "__main__":

    parser = argparse.ArgumentParser()
    parser.add_argument("courseid", help="Course id of the task to test")
    parser.add_argument("taskid", help="Task id of the task to test")
    parser.add_argument("-c", "--config", help="Configuration file", default="")
    parser.add_argument("-v", "--verbose", help="Display more output", action='store_true')
    args = parser.parse_args()

    # Read input argument
    verbose = args.verbose
    courseid = args.courseid
    taskid = args.taskid
    config = get_config(args.config)

    # Initialize course/task factory
    task_directory = config["tasks_directory"]
    course_factory, task_factory = create_factories(task_directory)

    # Initialize client
    zmq_context, asyncio_thread = start_asyncio_and_zmq()
    client = create_arch(config, task_directory, zmq_context)
    client.start()

    # Get the client synchronous
    job_manager = ClientSync(client)

    # Open the taskfile
    task = course_factory.get_course(courseid).get_task(taskid)

    # List inputfiles
    inputfiles = glob.glob(task_directory + "/" + courseid + "/" + taskid + '/*.test')

    # Wait for the agent to load containers
    time.sleep(2)

    for filename in inputfiles:
        filename = os.path.basename(filename)

        # Open the input file and merge with limits
        try:
            inputfile = open(task_directory + "/" + courseid + "/" + taskid + '/' + filename, 'r')
        except IOError as e:
            print(e)
            exit(2)

        data = inginious.common.custom_yaml.load(inputfile)
        launch_job(filename, data, inputfiles)

    client.close()
    sys.exit(0)
