#!/usr/bin/env python3
# Relational
# Copyright (C) 2010-2025  Salvo "LtWorf" Tomaselli
#
# Relational is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
# author Salvo "LtWorf" Tomaselli <tiposchi@tiscali.it>

import os
from sys import exit
import sys
import traceback

from relational import relation, parser, optimizer
from termcolor import cprint


COLOR_RED = 'red'
COLOR_GREEN = 'green'
COLOR_MAGENTA = 'magenta'
COLOR_CYAN = 'cyan'

print(relation)

rels = {}
examples_path = 'samples/'
tests_path = 'tests_dir/'


def readfile(fname):
    '''Reads a file as string and returns its content'''
    with open(fname, encoding='utf-8') as fd:
        expr = fd.read()
        return expr


def load_relations():
    '''Loads all the relations present in the directory indicated in the
    examples_path variable and stores them in the rels dictionary'''
    print("Loading relations")
    for i in os.listdir(examples_path):
        if i.endswith('.csv'):  # It's a relation, loading it

            # Naming the relation
            relname = i[:-4]

            print("Loading relation %s with name %s..." % (i, relname))

            rels[relname] = relation.Relation.load_csv('%s%s' % (examples_path, i))
            print('done')


def execute_tests():

    py_bad = 0
    py_good = 0
    py_tot = 0
    q_bad = 0
    q_good = 0
    q_tot = 0
    ex_bad = 0
    ex_good = 0
    ex_tot = 0
    f_tot = 0
    f_good = 0
    f_bad = 0

    for i in os.listdir(tests_path):
        if i.endswith('.query'):
            q_tot += 1
            if run_test(i[:-6]):
                q_good += 1
            else:
                q_bad += 1
        elif i.endswith('.python'):
            py_tot += 1
            if run_py_test(i[:-7]):
                py_good += 1
            else:
                py_bad += 1
        elif i.endswith('.py'):
            ex_tot += 1
            if run_exec_test(i[:-3]):
                ex_good += 1
            else:
                ex_bad += 1
        elif i.endswith('.fail'):
            f_tot += 1
            if run_fail_test(i[:-5]):
                f_good += 1
            else:
                f_bad += 1

    cprint("Resume of the results", COLOR_CYAN)

    cprint("Query tests", COLOR_MAGENTA)
    print("Total test count: %d" % q_tot)
    print("Passed tests: %d" % q_good)
    if q_bad > 0:
        cprint("Failed tests count: %d" % q_bad, COLOR_RED)

    cprint("Python tests", COLOR_MAGENTA)
    print("Total test count: %d" % py_tot)
    print("Passed tests: %d" % py_good)
    if py_bad > 0:
        cprint("Failed tests count: %d" % py_bad, COLOR_RED)

    cprint("Execute Python tests", COLOR_MAGENTA)
    print("Total test count: %d" % ex_tot)
    print("Passed tests: %d" % ex_good)
    if ex_bad > 0:
        cprint("Failed tests count: %d" % ex_bad, COLOR_RED)

    cprint("Execute fail tests", COLOR_MAGENTA)
    print("Total test count: %d" % f_tot)
    print("Passed tests: %d" % f_good)
    if f_bad > 0:
        cprint("Failed tests count: %d" % f_bad, COLOR_RED)

    cprint("Total results", COLOR_CYAN)
    if f_bad + q_bad + py_bad + ex_bad == 0:
        cprint("No failed tests", COLOR_GREEN)
        return 0
    else:
        cprint("There are %d failed tests" %
               (f_bad + py_bad + q_bad + ex_bad), COLOR_RED)
        return 1


def run_exec_test(testname):
    '''Runs a python test, which executes code directly rather than queries'''
    print("Running python test: ", end='')
    cprint(testname, COLOR_MAGENTA)

    glob = rels.copy()
    exp_result = {}

    expr = readfile('%s%s.py' % (tests_path, testname))

    try:
        exec(expr, glob)  # Evaluating the expression
        cprint('Test passed', COLOR_GREEN)
        return True
    except Exception as e:
        cprint('ERROR', COLOR_RED)
        cprint('=====================================', COLOR_RED)
        traceback.print_exc(file=sys.stdout)
        cprint('=====================================', COLOR_RED)
        return False


def run_py_test(testname):
    '''Runs a python test, which evaluates expressions directly rather than queries'''
    print("Running expression python test: ", end='')
    cprint(testname, COLOR_MAGENTA)
    exp_result = None
    result = None
    try:

        expr = readfile('%s%s.python' % (tests_path, testname))
        result = eval(expr, rels)

        expr = readfile('%s%s.result' % (tests_path, testname))
        exp_result = eval(expr, rels)

        if result == exp_result:
            cprint('Test passed', COLOR_GREEN)
            return True
    except:
        pass

    cprint('ERROR', COLOR_RED)
    cprint('=====================================', COLOR_RED)
    print("Expected %s" % exp_result.pretty_string())
    print("Got %s" % result.pretty_string())
    cprint('=====================================', COLOR_RED)
    return False

def run_fail_test(testname):
    '''Runs a test, which executes a query that is supposed to fail'''
    print("Running fail test: ", end='')
    cprint(testname, COLOR_MAGENTA)

    query = readfile('%s%s.fail' % (tests_path, testname)).strip()
    test_succeed = True

    try:
        expr = parser.parse(query)
        expr(rels)
        test_succeed = False
    except:
        pass

    try:
        o_query = optimizer.optimize_all(query, rels)
        o_expr = parser.parse(o_query)
        o_expr(rels)
        test_succeed = False
    except:
        pass

    try:
        c_expr = parser.tree(query).toCode()
        eval(c_expr, rels)
        test_succeed = False
    except:
        pass

    if test_succeed:
        cprint('Test passed', COLOR_GREEN)
    else:
        cprint('Test failed (by not raising any exception)', COLOR_RED)
    return test_succeed

def run_test(testname):
    '''Runs a specific test executing the file
    testname.query
    and comparing the result with
    testname.result
    The query will be executed both unoptimized and
    optimized'''
    print("Running test: ", end='')
    cprint(testname, COLOR_MAGENTA)

    query = None
    expr = None
    o_query = None
    o_expr = None
    result_rel = None
    result = None
    o_result = None

    try:
        result_rel = relation.Relation.load('%s%s.result' % (tests_path, testname))

        query = readfile('%s%s.query' % (tests_path, testname)).strip()
        o_query = optimizer.optimize_all(query, rels)

        expr = parser.parse(query)
        result = expr(rels)

        o_expr = parser.parse(o_query)
        o_result = o_expr(rels)

        c_expr = parser.tree(query).toCode()
        c_result = eval(c_expr, rels)

        if (o_result == result_rel) and (result == result_rel) and (c_result == result_rel):
            cprint('Test passed', COLOR_GREEN)
            return True
    except Exception as inst:
        traceback.print_exc(file=sys.stdout)
        print(inst)
        pass
    cprint('ERROR', COLOR_RED)
    print("Query: %s -> %s" % (query, expr))
    print("Optimized query: %s -> %s" % (o_query, o_expr))
    cprint('=====================================', COLOR_RED)
    cprint("Expected result", COLOR_GREEN)
    print(result_rel.pretty_string())
    cprint("Result", COLOR_RED)
    print(result.pretty_string())
    cprint("Optimized result", COLOR_RED)
    print(o_result.pretty_string())
    cprint("optimized result match %s" %
           str(result_rel == o_result), COLOR_MAGENTA)
    cprint("result match %s" %
           str(result == result_rel), COLOR_MAGENTA)
    cprint('=====================================', COLOR_RED)
    return False


if __name__ == '__main__':
    print("-> Starting testsuite for relational")
    load_relations()
    print("-> Starting tests")
    exit(execute_tests())
