# Copyright 2020 Intel Inc.
# 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 pecan
import wsme

from oslo_serialization import jsonutils
from wsme import types as wtypes

from cyborg import objects
from cyborg.agent.rpcapi import AgentAPI
from cyborg.api import expose
from cyborg.api.controllers import base
from cyborg.api.controllers import link
from cyborg.api.controllers import types
from cyborg.common import authorize_wsgi
from cyborg.common import exception as exc


class Deployable(base.APIBase):
    """API representation of a deployable.

    This class enforces type checking and value constraints, and converts
    between the internal object model and the API representation of
    a deployable.
    """

    uuid = types.uuid
    """The UUID of the deployable"""

    parent_id = types.integer
    """The parent ID of the deployable"""

    root_id = types.integer
    """The root ID of the deployable"""

    name = wtypes.text
    """The name of the deployable"""

    num_accelerators = types.integer
    """The number of accelerators of the deployable"""

    device_id = types.integer
    """The device on which the deployable is located"""

    attributes_list = wtypes.text
    """The json list of attributes of the deployable"""

    rp_uuid = types.uuid
    """The uuid of resource provider which represents this deployable"""

    driver_name = wtypes.text
    """The driver name of this deployables"""

    bitstream_id = wtypes.text
    """The id of bitstream which has been program in this deployable"""

    links = wsme.wsattr([link.Link], readonly=True)
    """A list containing a self link"""

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.fields = []
        for field in objects.Deployable.fields:
            self.fields.append(field)
            setattr(self, field, kwargs.get(field, wtypes.Unset))

    def convert_with_link(self, obj_dep):
        api_dep = Deployable(**obj_dep.as_dict())
        url = pecan.request.public_url
        api_dep.links = [
            link.Link.make_link('self', url, 'deployables', api_dep.uuid),
            link.Link.make_link(
                'bookmark', url, 'deployables', api_dep.uuid, bookmark=True
            ),
        ]
        query = {"deployable_id": obj_dep.id}
        attr_get_list = objects.Attribute.get_by_filter(
            pecan.request.context, query
        )
        attributes_list = []
        for exist_attr in attr_get_list:
            attributes_list.append({exist_attr.key: exist_attr.value})
        api_dep.attributes_list = jsonutils.dumps(attributes_list)
        return api_dep


class DeployableCollection(Deployable):
    """API representation of a collection of deployables."""

    deployables = [Deployable]
    """A list containing deployable objects"""

    def convert_with_links(self, obj_deps):
        collection = DeployableCollection()
        collection.deployables = [
            self.convert_with_link(obj_dep) for obj_dep in obj_deps
        ]
        return collection


class DeployablePatchType(types.JsonPatchType):
    _api_base = Deployable

    @staticmethod
    def internal_attrs():
        defaults = types.JsonPatchType.internal_attrs()
        return defaults + ['/name', '/num_accelerators']


class DeployablesController(base.CyborgController, DeployableCollection):
    """REST controller for Deployables."""

    _custom_actions = {'program': ['PATCH']}

    # TODO(s_shogo): We will update the policy of deployable APIs,
    # and using the new default policy rules in the W or later.
    @authorize_wsgi.authorize_wsgi("cyborg:deployable", "program", False)
    @expose.expose(Deployable, types.uuid, body=[DeployablePatchType])
    def program(self, uuid, program_info):
        """Program a new deployable(FPGA).

        :param uuid: The uuid of the target deployable.
        :param program_info: JSON string containing what to program.
        :raise: FPGAProgramError: If fpga program failed raise exception.
        :return: If fpga program success return deployable object.
        """

        image_uuid = program_info[0]['value'][0]['image_uuid']
        # TODO(s_shogo): In W or later version we plan to add schema check,
        # which will help checking input parameters' format.
        # So we can remove this validation in the future.
        try:
            types.UUIDType().validate(image_uuid)
        except Exception:
            raise

        obj_dep = objects.Deployable.get(pecan.request.context, uuid)
        obj_dev = objects.Device.get_by_device_id(
            pecan.request.context, obj_dep.device_id
        )
        hostname = obj_dev.hostname
        driver_name = obj_dep.driver_name
        cpid_list = obj_dep.get_cpid_list(pecan.request.context)
        controlpath_id = cpid_list[0]
        controlpath_id['cpid_info'] = jsonutils.loads(
            cpid_list[0]['cpid_info']
        )
        self.agent_rpcapi = AgentAPI()
        ret = self.agent_rpcapi.fpga_program(
            pecan.request.context,
            hostname,
            controlpath_id,
            image_uuid,
            driver_name,
        )
        if ret:
            return self.convert_with_link(obj_dep)
        else:
            raise exc.FPGAProgramError(ret=ret)

    @authorize_wsgi.authorize_wsgi("cyborg:deployable", "get_one")
    @expose.expose(Deployable, types.uuid)
    def get_one(self, uuid):
        """Retrieve information about the given deployable.

        :param uuid: UUID of a deployable.
        """
        obj_dep = objects.Deployable.get(pecan.request.context, uuid)
        return self.convert_with_link(obj_dep)

    @authorize_wsgi.authorize_wsgi("cyborg:deployable", "get_all")
    @expose.expose(DeployableCollection, wtypes.ArrayType(types.FilterType))
    def get_all(self, filters=None):
        """Retrieve a list of deployables.
        :param filters: a filter of FilterType to get deployables list by
        filter.
        """
        filters_dict = {}
        if filters:
            for filter in filters:
                filters_dict.update(filter.as_dict())
        context = pecan.request.context
        obj_deps = objects.Deployable.list(context, filters=filters_dict)
        return self.convert_with_links(obj_deps)
