# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. 
#   
# 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 numpy as np
import math
import paddle
from paddle.inference import Config
from paddle.inference import create_predictor


class PaddlePredictor(object):
    def __init__(self,
                 param_path,
                 model_path,
                 config,
                 delete_pass=[],
                 name='model'):
        super().__init__()
        run_mode = config.get("run_mode", "paddle")  # used trt or mkldnn
        shape_info_filename = os.path.join(
            config.get("output_dir", "output"),
            '{}_{}_shape_info.txt'.format(name, run_mode))

        self.predictor, self.inference_config, self.input_names, self.input_tensors, self.output_tensors = self.create_paddle_predictor(
            param_path,
            model_path,
            batch_size=config['batch_size'],
            run_mode=run_mode,
            device=config.get("device", "CPU"),
            min_subgraph_size=config["min_subgraph_size"],
            shape_info_filename=shape_info_filename,
            trt_calib_mode=config["trt_calib_mode"],
            cpu_threads=config["cpu_threads"],
            trt_use_static=config["trt_use_static"],
            delete_pass=delete_pass)

    def create_paddle_predictor(self,
                                param_path,
                                model_path,
                                batch_size=1,
                                run_mode='paddle',
                                device='CPU',
                                min_subgraph_size=3,
                                shape_info_filename=None,
                                trt_calib_mode=False,
                                cpu_threads=6,
                                trt_use_static=False,
                                delete_pass=[]):
        if not os.path.exists(model_path) or not os.path.exists(param_path):
            raise ValueError(
                f"inference model: {model_path} or param: {param_path} does not exist, please check again..."
            )
        assert run_mode in [
            "paddle",
            "trt_fp32",
            "trt_fp16",
            "trt_int8",
            "mkldnn",
        ], "The run_mode must be 'paddle', 'trt_fp32', 'trt_fp16', 'trt_int8', 'mkldnn', but received run_mode: {}".format(
            run_mode)
        config = Config(model_path, param_path)
        if device == 'GPU':
            config.enable_use_gpu(200, 0)
        else:
            config.disable_gpu()
            if 'mkldnn' in run_mode:
                try:
                    config.enable_mkldnn()
                    config.set_cpu_math_library_num_threads(cpu_threads)
                except Exception as e:
                    print(
                        "The current environment does not support `mkldnn`, so disable mkldnn."
                    )
                    pass

        precision_map = {
            'trt_int8': Config.Precision.Int8,
            'trt_fp32': Config.Precision.Float32,
            'trt_fp16': Config.Precision.Half
        }
        if run_mode in precision_map.keys():
            config.enable_tensorrt_engine(
                workspace_size=(1 << 25) * batch_size,
                max_batch_size=batch_size,
                min_subgraph_size=min_subgraph_size,
                precision_mode=precision_map[run_mode],
                use_static=trt_use_static,
                use_calib_mode=trt_calib_mode)

            if shape_info_filename is not None:
                if not os.path.exists(shape_info_filename):
                    config.collect_shape_range_info(shape_info_filename)
                    print(
                        f"collect dynamic shape info into : {shape_info_filename}"
                    )
                else:
                    print(
                        f"dynamic shape info file( {shape_info_filename} ) already exists, not need to generate again."
                    )
                config.enable_tuned_tensorrt_dynamic_shape(shape_info_filename,
                                                           True)

        # disable print log when predict
        config.disable_glog_info()
        for del_p in delete_pass:
            config.delete_pass(del_p)
        # enable shared memory
        config.enable_memory_optim()
        config.switch_ir_optim(True)
        # disable feed, fetch OP, needed by zero_copy_run
        config.switch_use_feed_fetch_ops(False)
        predictor = create_predictor(config)

        # get input and output tensor property
        input_names = predictor.get_input_names()
        input_tensors = []
        output_tensors = []
        for input_name in input_names:
            input_tensor = predictor.get_input_handle(input_name)
            input_tensors.append(input_tensor)
        output_names = predictor.get_output_names()
        for output_name in output_names:
            output_tensor = predictor.get_output_handle(output_name)
            output_tensors.append(output_tensor)
        return predictor, config, input_names, input_tensors, output_tensors

    def get_input_names(self):
        return self.input_names

    def run(self, x):
        if not isinstance(x, (list, tuple)):
            x = [x]

        for idx in range(len(x)):
            self.input_tensors[idx].copy_from_cpu(x[idx])
        self.predictor.run()
        result = []

        self.predictor.run()
        output_names = self.predictor.get_output_names()
        for name in output_names:
            output = self.predictor.get_output_handle(name).copy_to_cpu()
            result.append(output)
        return result
