# Copyright 2019 Huawei Technologies Co., Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import os
import logging
from gen_random import random_gaussian
import numpy as np
from akg.utils import kernel_exec as utils
from test_op import vector_matmul


logging.basicConfig(level=logging.DEBUG)


def np_matmul(matrix_a, matrix_b, trans_a=False, trans_b=False):
    if trans_a:
        matrix_a = matrix_a.transpose(1, 0)
    if trans_b:
        matrix_b = matrix_b.transpose(1, 0)
    m, k_a = matrix_a.shape
    k_b, n = matrix_b.shape
    if k_a != k_b:
        raise RuntimeError("matrix_a: %d %d vs matrix_b: %d %d" % (m, k_a, k_b, n))
    result = np.dot(matrix_a, matrix_b)
    return result


def gen_data(m, n, k, trans_a, trans_b, dtype):
    shape_x, shape_y = vector_matmul.get_shape(m, n, k, trans_a, trans_b)
    matrix_a = random_gaussian(shape_x, miu=0.5, sigma=0.01).astype(dtype)
    # matrix_b = random_gaussian(shape_y, miu=0.5, sigma=0.01).astype(dtype)
    # matrix_a = np.ones(shape_x, dtype=dtype)
    matrix_b = np.ones(shape_y, dtype=dtype)
    res = np_matmul(matrix_a, matrix_b, trans_a, trans_b)
    return matrix_a, matrix_b, res


def get_name(caseIndex=1, name="leftMatrix", m=0, n=0, k=0, trans_a=False, trans_b=False):
    res = "{}_{}_{}_{}_{}_{}_{}.bin".format(caseIndex, name, m, n, k, trans_a, trans_b)
    return res


def read_from_file(case_index, m, n, k, trans_a, trans_b, dtype):
    cur_path = os.path.abspath('.')
    benchmark_path, tmp = cur_path.split("ci_test")
    benchmark_path += "ci_test/AT-Benchmark/poly_benchmark/vector_matmul_benchmark/"

    # print benchmark_path
    left_matrix_name = get_name(case_index, "leftMatrix", m, n, k, trans_a, trans_b)
    right_matrix_name = get_name(case_index, "rightMatrix", m, n, k, trans_a, trans_b)
    result_name = get_name(case_index, "result", m, n, k, trans_a, trans_b)

    m_a_shape, m_b_shape = vector_matmul.get_shape(m, n, k, trans_a, trans_b)
    m_a = np.fromfile(benchmark_path + left_matrix_name, dtype=dtype).reshape(m_a_shape)
    m_b = np.fromfile(benchmark_path + right_matrix_name, dtype=dtype).reshape(m_b_shape)
    res_shape = (m, n)
    res = np.fromfile(benchmark_path + result_name, dtype=dtype).reshape(res_shape)
    return m_a, m_b, res


def vector_matmul_data(case_index, m, n, k, trans_a, trans_b, read_data, dump_data, dtype, debug_logging=False):
    m_a = ()
    m_b = ()
    bench_mark = ()

    if read_data:
        logging.debug("read from file!")
        m_a, m_b, bench_mark = read_from_file(case_index, m, n, k, trans_a, trans_b, dtype)
    else:
        m_a, m_b, bench_mark = gen_data(m, n, k, trans_a, trans_b, dtype)

    if dump_data:
        left_matrix_name = get_name(case_index, "leftMatrix", m, n, k, trans_a, trans_b)
        right_matrix_name = get_name(case_index, "rightMatrix", m, n, k, trans_a, trans_b)
        result_name = get_name(case_index, "result", m, n, k, trans_a, trans_b)
        m_a.tofile(left_matrix_name)
        m_b.tofile(right_matrix_name)
        bench_mark.tofile(result_name)

    if debug_logging:
        logging.debug("m_a shape:{}".format(m_a.shape))
        logging.debug("m_b shape:{}".format(m_b.shape))
        logging.debug(type(m_a))

    return m_a, m_b, bench_mark


def result_compare(actual, bench_mark, batch_tuple, M, N, K, r_tol=5e-3):
    output_shape = (M, N)

    error = 0
    count = 0
    lastErr = -2
    continueErr = 0
    maxContinue = -1
    maxEnd = 0
    logging.debug(actual.shape)
    logging.debug(bench_mark.shape)

    for m in range(output_shape[0]):
        for n in range(output_shape[1]):
            a = actual[m, n]
            b = bench_mark[m, n]
            if(abs(a - b) > abs(b) * r_tol):
                error += 1

                if lastErr + 1 == count:
                    continueErr += 1
                else:
                    if continueErr > maxContinue:
                        maxContinue = continueErr
                        maxEnd = lastErr
                    continueErr = 1
                lastErr = count

                # if a != 0.0:
                logging.debug("count: %6d expect: %20f actual: %20f %20.2f%%" % (count, b, a, abs(b - a) / b * 100))
            count += 1
    if continueErr > maxContinue:
        maxContinue = continueErr
        maxEnd = lastErr
    logging.debug("error num: %d/%d (%.2f%%)" % (error, count, 100.0 * error / count))
    logging.debug("longest error range: [%d, %d]" % (maxEnd - maxContinue + 1, maxEnd))
    if maxContinue >= 16:
        return False
    logging.debug("\n\n******************** test ok *****************\n\n")
    return True


def vector_matmul_run(case_index, m, n, k, trans_a, trans_b, read_data, dump_data, dtype, kernel_name, attrs):
    batch_tuple = (1, )
    # m = (m+15)//16*16
    # n = (n+15)//16*16
    # k = (k+15)//16*16

    mod, out_shape = vector_matmul.vector_matmul(m, n, k, trans_a, trans_b, dtype, kernel_name, attrs)
    utils.create_cce(kernel_name, "./", mod.imported_modules[0].get_source())

    # Generate data
    m_a, m_b, bench_mark = vector_matmul_data(case_index, m, n, k, trans_a, trans_b, read_data, dump_data, dtype)

    # mod launch
    output = np.full(out_shape, np.nan, dtype=dtype)
    output = utils.mod_launch(mod, (m_a, m_b, output), expect=batch_tuple)

    # compare result
    compare_result = result_compare(output, bench_mark, batch_tuple, m, n, k, r_tol=1e-2)
    return (m_a, m_b), output, bench_mark, compare_result
