#!/usr/bin/env python3
# coding: utf-8
# 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.

"""
The build utils in python.
This module provides the functions to transform schedule to
LoweredFunc and compiled Module.
"""
from __future__ import absolute_import as _abs
import sys
import logging
from akg.utils import validation_check as vc_util
import akg.tvm
from akg.tvm import _api_internal
from akg.tvm import schedule


tuning_spaces = None
help_tiling_level = {
    "None": 0, "General": 1, "Candidates": 2, "Tuning": 3
}
EMPTY_CODE = 0
L0_DEFAULT_TILING = 1


def dump_tiling_info(level):
    """Dump tiling info."""
    if tuning_spaces is None:
        return
    logging.getLogger().setLevel(logging.INFO)
    if level >= help_tiling_level["General"]:
        logging.info("==========General tiling help info=============")
        indice = tuning_spaces["index"]
        if isinstance(indice, list):
            for i in range(len(indice)):
                info = "index %d, axis %d, l1_tile_ranges [%d, %d](jump by %d),l0_tile_ranges [%d, %d](jump by %d)"
                logging.info(info, tuning_spaces["index"][i][0], tuning_spaces["index"][i][1],
                             tuning_spaces["l1_range"][i][0], tuning_spaces["l1_range"][i][1],
                             tuning_spaces["l1_mod"][i][0], tuning_spaces["l0_range"][i][0],
                             tuning_spaces["l0_range"][i][1], tuning_spaces["l0_mod"][i][0])
            logging.info("===============================================")
        elif isinstance(indice, int) and indice == EMPTY_CODE:
            logging.info("Empty tiling space.")

    if level >= help_tiling_level["Candidates"]:
        logging.info("")
        logging.info("==========Detailed tiling help info(Only L1)=============")
        logging.info("index 0 has %d candidate(s) tiling factors", len(tuning_spaces["tuning_space"]))
        tuning_spaces_len = len(tuning_spaces["tuning_space"])
        for i in range(tuning_spaces_len):
            info = "candicate %d:("
            for l1_candidate in tuning_spaces["tuning_space"][i]:
                info += ("(" + str(l1_candidate) + ", " + str(L0_DEFAULT_TILING) + "),")
            info += ")"
            logging.info(info, i)
    logging.info("=============================================================")
    logging.info("")
    logging.info("Please read this tiling help info and set tiling factor.")
    logging.info("And then set attr \"help_tiling\" value to 0 and re-run.")
    logging.info("Exit.")
    sys.exit()


def build_config(**kwargs):
    """build config."""
    return akg.tvm.build_config(**kwargs)


@vc_util.check_input_type(schedule.Schedule, (list, tuple), (list, tuple), str,
                          (dict, type(None)), (dict, type(None)), bool, bool, bool, str)
def lower(sch, args, shape_params=None, name="default_function", binds=None, attrs=None,
          simple_mode=False, polyhedral=False, tuning=False, target="cce"):
    """Lowering function."""
    tmp_binds = None
    if binds is not None:
        tmp_binds = None if not bool(binds) else binds
    tmp_attrs = None
    if attrs is not None:
        tmp_attrs = None if not bool(attrs) else attrs
    if shape_params is None:
        shape_params = []

    cfg = _api_internal._GetCurrentBuildConfig()
    ret = _api_internal._Lower(sch, args, shape_params, name,
                               tmp_binds, tmp_attrs, simple_mode,
                               polyhedral, tuning, target, cfg)

    level = tmp_attrs.get("help_tiling")
    if tuning or (level is not None and level > help_tiling_level['None']):
        level = help_tiling_level['Tuning'] if tuning else level
        global tuning_spaces
        tuning_spaces = {}
        tuning_spaces["index"] = ret.index_table.asnumpy().tolist()
        tuning_spaces["l1_range"] = ret.l1_tile_range_table.asnumpy().tolist()
        tuning_spaces["l0_range"] = ret.l0_tile_range_table.asnumpy().tolist()
        tuning_spaces["l1_mod"] = ret.l1_tile_mod_table.asnumpy().tolist()
        tuning_spaces["l0_mod"] = ret.l0_tile_mod_table.asnumpy().tolist()
        if level >= help_tiling_level["Candidates"]:
            tuning_spaces["tuning_space"] = ret.tiling_candidate.asnumpy().tolist()
        if not tuning:
            dump_tiling_info(level)
    return ret


@vc_util.check_input_type(schedule.Schedule, (list, tuple), (list, tuple, type(None)), str,
                          (dict, type(None)), (dict, type(None)), bool, str)
def build_to_func(inputs, args, shape_params=None, name="default_function",
                  binds=None, attrs=None, polyhedral=False, target="cce"):
    """Build module."""
    tmp_binds = None
    if binds is not None:
        tmp_binds = None if not bool(binds) else binds
    tmp_attrs = None
    if attrs is not None:
        tmp_attrs = None if not bool(attrs) else attrs
    for arg in args:
        vc_util.tensor_max_size_check(arg)
    if shape_params is None:
        shape_params = []
    cfg = _api_internal._GetCurrentBuildConfig()
    return _api_internal._BuildToFunc(inputs, args, shape_params, name, tmp_binds, tmp_attrs,
                                      polyhedral, target, cfg)

@vc_util.check_input_type(schedule.Schedule, (list, tuple), str, (list, tuple), str,
                          (dict, type(None)), (dict, type(None)), bool)
def build(inputs, args, target='cce', shape_params=None, name="default_function",
          binds=None, attrs=None, polyhedral=False):
    tmp_rst = build_to_func(inputs, args, shape_params=shape_params, name=name, binds=binds,
                            attrs=attrs, polyhedral=polyhedral, target=target)

    return _api_internal._BuildToModule(tmp_rst, target)
