#!/usr/bin/env python
# coding: utf-8
#
# Copyright (c) 2020-2021 Huawei Device Co., Ltd. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this list of
#    conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice, this list
#    of conditions and the following disclaimer in the documentation and/or other materials
#    provided with the distribution.
#
# 3. Neither the name of the copyright holder nor the names of its contributors may be used
#    to endorse or promote products derived from this software without specific prior written
#    permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
# OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

import os
import platform
import shutil
import subprocess
import sys
import base64

SOURCE_ROOT = "../../"
CMAKE_GEN_PATH = "cmake-build-debug/"
WORK_DIR = ""
ERROR_COLOR_PREFIX = "\033[31m"
ERROR_COLOR_END = "\033[0m"
ALIGNED_PARM = ''


def setup_hcgen_compiler():
    global SOURCE_ROOT
    global CMAKE_GEN_PATH

    if len(sys.argv) > 1:
        hcgen_path = os.path.abspath(sys.argv[1])
        if hcgen_path.find('hc-gen') >= 0 and os.access(hcgen_path, os.X_OK):
            print('use specified hsc:' + hcgen_path)
            return hcgen_path

    source_root = '../../'
    compiler_name = "hc-gen"
    if platform.system() == "Windows":
        source_root = source_root.replace("/", "\\")
        compiler_name += ".exe"

    source_root = os.path.abspath(os.path.join(WORK_DIR, source_root))
    hcgen_path = os.path.join(source_root, compiler_name)
    if not os.access(hcgen_path, os.X_OK):
        hcgen_path = os.path.join(source_root, CMAKE_GEN_PATH, compiler_name)
        if not os.access(hcgen_path, os.X_OK):
            print("Error: hcgen not found, please make first")
            exit(1)

    return hcgen_path


def index_case(case_path):
    cases = []
    for directory in os.listdir(case_path):
        if os.path.isdir(os.path.join(case_path, directory)):
            cases.append(directory)
    cases.sort()
    return cases


def save_compile_result(mode, case_name, status, output):
    result_file_name = os.path.join(WORK_DIR, case_name,
                                    'golden_%s_compile_result.txt' % mode)
    with open(result_file_name, 'w') as result_output:
        status_str = "[compile exit status]:" + str(status)
        output_str = "\n[compile console output]:\n" + output
        result_output.write(status_str)
        result_output.write(output_str)


def gen_decompile_golden(hcgen_path, case_name):
    source_file = os.path.join(WORK_DIR, case_name, 'golden.hcb')
    target_file = os.path.join(WORK_DIR, case_name, 'golden')
    command = "%s -o %s -d %s" % (hcgen_path, target_file, source_file)
    status, output = subprocess.getstatusoutput(command)
    if status:
        print('case %s decompile fail:\n %s, status=%d' %
              (case_name, output, status))

    return status == 0


def recode_hcb_file(file_path):
    with open(file_path, 'rb') as hcb_file:
        content = hcb_file.read()
        hcb_file.close()

    with open(file_path, 'wb') as hcb_file:
        base64_content = base64.b64encode(content)
        hcb_file.write(base64_content)
        hcb_file.close()


def build_binary(hcgen_path, case_name):
    source_file = os.path.join(WORK_DIR, case_name, 'case.hcs')
    target_file = os.path.join(WORK_DIR, case_name, 'golden')
    command = "%s %s -o %s %s" % \
              (hcgen_path, ALIGNED_PARM, target_file, source_file)
    status, output = subprocess.getstatusoutput(command)
    if case_name.endswith('_ei'):
        if status == 0:
            print("CASE_ERROR:error identify case " + case_name
                  + "expect build fail but success at binary mode")
            return False
    elif status != 0:
        print("CASE_ERROR:case " + case_name
              + " expect build success but failed at binary mode")
        return False
    output = output.replace(WORK_DIR, ".").replace('\\', '/') \
        .replace(ERROR_COLOR_PREFIX, "") \
        .replace(ERROR_COLOR_END, "").strip()

    save_compile_result('binary', case_name, status, output)

    result = True
    if status == 0:
        result = gen_decompile_golden(hcgen_path, case_name)
        if result:
            recode_hcb_file(target_file + '.hcb')
    return result


def build_text(hcgen_path, case_name):
    source_file = os.path.join(WORK_DIR, case_name, 'case.hcs')
    target_file = os.path.join(WORK_DIR, case_name, 'golden')
    command = "%s -t -o %s %s" % (hcgen_path, target_file, source_file)
    status, output = subprocess.getstatusoutput(command)
    if case_name.endswith('_ei') or case_name.find('node_duplicate_name') >= 0:
        if status == 0:
            print("CASE_ERROR:error identify case "
                  + case_name + " expect build fail but success at text mode")
            print(output)
            return False
    elif status != 0:
        print("CASE_ERROR:case " + case_name
              + " expect build success but failed at text mode")
        print(output)
        return False
    output = output.replace(WORK_DIR, ".").replace('\\', '/') \
        .replace(ERROR_COLOR_PREFIX, "") \
        .replace(ERROR_COLOR_END, "").strip()

    save_compile_result('text', case_name, status, output)

    if status == 0:
        os.rename(target_file + '.c', target_file + '.c.gen')
        os.rename(target_file + '.h', target_file + '.h.gen')
    return True


def build_macro(hcgen_path, case_name):
    source_file = os.path.join(WORK_DIR, case_name, 'case.hcs')
    target_file = os.path.join(WORK_DIR, case_name, 'macro')
    command = "%s -m -o %s %s" % (hcgen_path, target_file, source_file)
    status, output = subprocess.getstatusoutput(command)
    if case_name.endswith('_ei'):
        if status == 0:
            print("CASE_ERROR:error identify case "
                  + case_name + " expect build fail but success at macro mode")
            print(output)
            return False
    elif status != 0:
        print("CASE_ERROR:case " + case_name
              + " expect build success but failed at macro mode")
        print(output)
        return False
    output = output.replace(WORK_DIR, ".").replace('\\', '/') \
        .replace(ERROR_COLOR_PREFIX, "") \
        .replace(ERROR_COLOR_END, "").strip()

    save_compile_result('macro', case_name, status, output)

    if status == 0:
        os.rename(target_file + '.h', target_file + '.h.gen')
    return True


def build_cases(hcgen_path, cases):
    index = 1
    failed_cases = []
    for case in cases:
        print('[%02d/%d] build %s' % (index, len(cases), case))
        text_compile = build_text(hcgen_path, case)
        binary_compile = build_binary(hcgen_path, case)
        macro_compile = build_macro(hcgen_path, case)
        if not text_compile or not binary_compile or not macro_compile:
            failed_cases.append(case)
        index += 1

    print("\nUpdate cases result :\ntotal case: %d, failed: %d, success: %d" %
          (len(cases), len(failed_cases), len(cases) - len(failed_cases)))
    if len(failed_cases) > 0:
        print("Failed case as below:")
        for case in failed_cases:
            print(case)


def setup_work_dir():
    global WORK_DIR
    pwd = os.path.abspath(sys.argv[0])
    pwd = pwd[:pwd.rfind(os.sep)]
    WORK_DIR = pwd


def clean_up():
    list_dirs = os.walk(WORK_DIR)
    for root, dirs, files in list_dirs:
        for file in files:
            if file.startswith('golden'):
                os.remove(os.path.join(root, file))

    temp_dir = os.path.join(WORK_DIR, 'temp')
    if os.path.exists(temp_dir):
        shutil.rmtree(temp_dir)


if __name__ == "__main__":
    if len(sys.argv) > 1 and sys.argv[-1] == '--align':
        ALIGNED_PARM = ' -a '
    setup_work_dir()
    hc_gen = setup_hcgen_compiler()
    clean_up()
    print("hc-gen path : " + hc_gen)
    test_case_list = index_case(WORK_DIR)
    build_cases(hc_gen, test_case_list)