# ==============================================================================
# Binaries and/or source for the following packages or projects
# are presented under one or more of the following open source licenses:
# check.py    The OpenLane-V2 Dataset Authors    Apache License, Version 2.0
#
# Contact wanghuijie@pjlab.org.cn if you have any issue.
#
# Copyright (c) 2023 The OpenLane-v2 Dataset 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.
# ==============================================================================

from functools import reduce

import numpy as np
from iso3166 import countries


def check_results(results: dict) -> None:
    r"""
    Check format of results.

    Parameters
    ----------
    results : dcit
        Dict storing predicted results.

    """
    valid = True

    if not isinstance(results, dict):
        raise Exception(f'Type of result should be dict')

    for key in ['method', 'e-mail', 'institution / company', 'country / region']:
        if key in results:
            if not isinstance(results[key], str):
                raise Exception(f'Type of value in key [{key}] should be str')
            if key == 'country / region':
                try:
                    countries.get(results[key])
                except Exception:
                    raise Exception(f'Please specify a valid [{key}] according to ISO3166')
        else:
            valid = False
            print(f'\n*** Missing key [{key}] for a valid submission ***\n')

    for key in ['authors']:
        if key in results:
            if not isinstance(results[key], list):
                raise Exception(f'Type of value in key [{key}] should be list')
            if len(results[key]) > 10:
                raise Exception(f'The number of authors should not exceed 10')
        else:
            valid = False
            print(f'\n*** Missing key [{key}] for a valid submission ***\n')

    for key in ['results']:
        if key not in results:
            raise Exception(f'Miss key [{key}].')
        if not isinstance(results[key], dict):
            raise Exception(f'Type of value in key [{key}] should be dict')

    for token, predictions in results['results'].items():
        if not isinstance(predictions, dict):
            raise Exception(f'Type of value in key [results/{token}] should be dict')
        predictions = predictions['predictions']
        if not isinstance(predictions, dict):
            raise Exception(f'Type of value in key [results/{token}/predictions] should be dict')

        ids = {}
        for key in ['lane_centerline', 'traffic_element']:
            if key not in predictions:
                raise Exception(f'Miss key [results/{token}/predictions/{key}].')
            if not isinstance(predictions[key], list):
                raise Exception(f'Type of value in key [results/{token}/predictions/{key}] should be list')

            for instance in predictions[key]:
                for k in ['id', 'points', 'confidence']:
                    if k not in instance:
                        raise Exception(f'Miss key [results/{token}/predictions/{key}/k].')
                if key == 'traffic_element':
                    if 'attribute' not in instance:
                        raise Exception(f'Miss key [results/{token}/predictions/{key}/k].')

                points = instance['points']
                if not isinstance(points, np.ndarray):
                    raise Exception(
                        f'Type of value in key [results/{token}/predictions/{key}/{instance["id"]}] should be np.ndarray')
                points = np.array(points)
                if key == 'lane_centerline' and not (points.ndim == 2 and points.shape[1] == 3):
                    raise Exception(
                        f'Shape of points in instance [results/{token}/predictions/{key}/{instance["id"]}] should be (#points, 3) but not {points.shape}')
                if key == 'traffic_element' and not (points.ndim == 2 and points.shape == (2, 2)):
                    raise Exception(
                        f'Shape of points in instance [results/{token}/predictions/{key}/{instance["id"]}] should be (2, 2) but not {points.shape}')

            ids[key] = [instance['id'] for instance in predictions[key]]

        ids_check = reduce(lambda x, y: x + y, ids.values(), [])
        if len(set(ids_check)) != len(ids_check):
            raise Exception(f'IDs are not unique in [results/{token}/predictions]')

        if 'topology_lclc' not in predictions:
            raise Exception(f'Miss key [results/{token}/predictions/topology_lclc].')
        topology_lclc = predictions['topology_lclc']
        if not isinstance(topology_lclc, np.ndarray):
            raise Exception(f'Type of value in key [results/{token}/predictions/topology_lclc] should be np.ndarray')
        topology_lclc = np.array(topology_lclc)
        if not (topology_lclc.ndim == 2 and topology_lclc.shape[0] == len(ids['lane_centerline']) and
                topology_lclc.shape[1] == len(ids['lane_centerline'])):
            raise Exception(
                f'Shape of adjacent matrix of [results/{token}/predictions/topology_lclc] should be (#lane_centerline, #lane_centerline) but not {topology_lclc.shape}')

        if 'topology_lcte' not in predictions:
            raise Exception(f'Miss key [results/{token}/predictions/topology_lcte].')
        topology_lcte = predictions['topology_lcte']
        if not isinstance(topology_lcte, np.ndarray):
            raise Exception(f'Type of value in key [results/{token}/predictions/topology_lcte] should be np.ndarray')
        topology_lcte = np.array(topology_lcte)
        if not (topology_lcte.ndim == 2 and topology_lcte.shape[0] == len(ids['lane_centerline']) and
                topology_lcte.shape[1] == len(ids['traffic_element'])):
            raise Exception(
                f'Shape of adjacent matrix of [results/{token}/predictions/topology_lcte] should be (#lane_centerline, #traffic_element) but not {topology_lcte.shape}')

    return valid
