# coding=utf-8
# Copyright 2021 The OneFlow 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 oneflow as flow
from PIL import Image

from libai.config import instantiate
from libai.data.structures import DistTensorData, Instance
from libai.inference.basic import BasePipeline


class ImageClassificationPipeline(BasePipeline):
    def __init__(
        self,
        config_file,
        data_parallel=None,
        tensor_parallel=None,
        pipeline_parallel=None,
        pipeline_stage_id=None,
        pipeline_num_layers=None,
        model_path=None,
        mode="libai",
        **kwargs,
    ):
        super().__init__(
            config_file,
            data_parallel,
            tensor_parallel,
            pipeline_parallel,
            pipeline_stage_id,
            pipeline_num_layers,
            model_path,
            mode,
            **kwargs,
        )
        if "num_classes" in self.cfg.model:
            self.num_classes = self.cfg.model.num_classes
        elif "num_classes" in self.cfg.model.cfg:
            self.num_classes = self.cfg.model.cfg.num_classes
        else:
            raise AttributeError("The model's config must contain num_classes")
        label2id = self.label2id(self.num_classes)
        self.id2label = {ind: label for label, ind in label2id.items()}
        self.transform = instantiate(self.cfg.dataloader.test[0].dataset.transform)

    def _parse_parameters(self, **pipeline_parameters):
        preprocess_params = {}
        forward_params = {}
        postprocess_params = {**pipeline_parameters}
        return preprocess_params, forward_params, postprocess_params

    def preprocess(
        self,
        inputs,
        **kwargs,
    ) -> dict:
        assert os.path.exists(inputs), "inputs must be an existing image path!"
        with open(inputs, "rb") as f:
            img = Image.open(f).convert("RGB")
        img = self.transform(img)
        img = img.unsqueeze(0)

        # to global tensor
        model_input = Instance(
            images=DistTensorData(img),
        )
        mdoel_input_dict = {}
        for key, value in model_input.get_fields().items():
            value.to_global()
            mdoel_input_dict[key] = value.tensor
        return mdoel_input_dict

    def forward(self, mdoel_input_dict) -> dict:
        model_outputs_dict = self.model(**mdoel_input_dict)
        return model_outputs_dict

    def postprocess(
        self, model_outputs_dict, function_to_apply=None, return_all_scores=False, **kwargs
    ) -> dict:
        # prepare
        num_labels = self.num_classes
        if function_to_apply is not None:
            function_to_apply = function_to_apply.lower()
            assert function_to_apply in [
                "sigmoid",
                "softmax",
                "none",
            ], f"Unrecognized `function_to_apply` argument: {function_to_apply}"
        else:
            if num_labels == 1:
                function_to_apply = "sigmoid"
            elif num_labels > 1:
                function_to_apply = "softmax"

        # process, logits: [num_labels]
        logits = model_outputs_dict["prediction_scores"][0]

        if function_to_apply == "sigmoid":
            scores = flow.sigmoid(logits)
        elif function_to_apply == "softmax":

            scores = flow.softmax(logits)
        else:
            scores = logits
        scores = scores.detach().numpy()

        if return_all_scores:
            return [
                {"label": self.id2label[i], "score": score.item()} for i, score in enumerate(scores)
            ]
        else:
            return {
                "label": self.id2label[scores.argmax().item()],
                "score": scores.max().item(),
            }

    def label2id(self, num_classes):
        """
        Args:
            num_classes (int): the number of total classes
        Returns:
            labels (list): a dict contains all the labels for inference,
                           each item should be the form as follows:
                           {
                               "tench": 0,
                               "tiger": 1,
                               "xxx", n,
                           }

        """
        from libai.inference.utils.imagenet_class import IMAGENET_LABELS as labels

        assert num_classes == len(labels), "number of labels must be equal to num_classes"
        return {label: i for (i, label) in enumerate(labels)}


if __name__ == "__main__":
    pipeline = ImageClassificationPipeline("/home/chengpeng/config.yaml", 1, 1, 1)
    print(pipeline("data_test/inference_test_data/ILSVRC2012_val_00000293.JPEG"))
