# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements.  See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You 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.
"""
VCL driver
"""

import time

from libcloud.common.base import ConnectionUserAndKey
from libcloud.common.types import LibcloudError, InvalidCredsError
from libcloud.compute.base import Node, NodeSize, NodeImage, NodeDriver
from libcloud.common.xmlrpc import XMLRPCResponse, XMLRPCConnection
from libcloud.compute.types import Provider, NodeState


class VCLResponse(XMLRPCResponse):
    exceptions = {
        "VCL_Account": InvalidCredsError,
    }


class VCLConnection(XMLRPCConnection, ConnectionUserAndKey):
    endpoint = "/index.php?mode=xmlrpccall"

    def add_default_headers(self, headers):
        headers["X-APIVERSION"] = "2"
        headers["X-User"] = self.user_id
        headers["X-Pass"] = self.key
        return headers


class VCLNodeDriver(NodeDriver):
    """
    VCL node driver

    :keyword   host: The VCL host to which you make requests(required)
    :type      host: ``str``
    """

    NODE_STATE_MAP = {
        "ready": NodeState.RUNNING,
        "failed": NodeState.TERMINATED,
        "timedout": NodeState.TERMINATED,
        "loading": NodeState.PENDING,
        "time": NodeState.PENDING,
        "future": NodeState.PENDING,
        "error": NodeState.UNKNOWN,
        "notready": NodeState.PENDING,
        "notavailable": NodeState.TERMINATED,
        "success": NodeState.PENDING,
    }

    connectionCls = VCLConnection
    name = "VCL"
    website = "http://incubator.apache.org/vcl/"
    type = Provider.VCL

    def __init__(self, key, secret, secure=True, host=None, port=None, *args, **kwargs):
        """
        :param    key:    API key or username to used (required)
        :type     key:    ``str``

        :param    secret: Secret password to be used (required)
        :type     secret: ``str``

        :param    secure: Whether to use HTTPS or HTTP.
        :type     secure: ``bool``

        :param    host: Override hostname used for connections. (required)
        :type     host: ``str``

        :param    port: Override port used for connections.
        :type     port: ``int``

        :rtype: ``None``
        """
        if not host:
            raise Exception(
                "When instantiating VCL driver directly " + "you also need to provide host"
            )

        super().__init__(key, secret, secure=True, host=None, port=None, *args, **kwargs)

    def _vcl_request(self, method, *args):
        res = self.connection.request(method, *args).object
        if res["status"] == "error":
            raise LibcloudError(res["errormsg"], driver=self)
        return res

    def create_node(self, image, start=None, length="60"):
        """Create a new VCL reservation
        size and name ignored, image is the id from list_image

        @inherits: :class:`NodeDriver.create_node`

        :keyword    image: image is the id from list_image
        :type       image: ``str``

        :keyword    start: start time as unix timestamp
        :type       start: ``str``

        :keyword    length: length of time in minutes
        :type       length: ``str``
        """

        # Special case for xmlrpclib not handling 64 bit integers when writing
        # XML - we always  cast value to string.
        start = start or str(time.time())
        length = length or "60"

        res = self._vcl_request("XMLRPCaddRequest", image.id, start, length)

        return Node(
            id=res["requestid"],
            name=image.name,
            state=self.NODE_STATE_MAP[res["status"]],
            public_ips=[],
            private_ips=[],
            driver=self,
            image=image.name,
        )

    def destroy_node(self, node):
        """
        End VCL reservation for the node passed in.
        Throws error if request fails.

        :param  node: The node to be destroyed
        :type   node: :class:`Node`

        :rtype: ``bool``
        """
        try:
            self._vcl_request("XMLRPCendRequest", node.id)
        except LibcloudError:
            return False
        return True

    def _to_image(self, img):
        return NodeImage(id=img["id"], name=img["name"], driver=self.connection.driver)

    def list_images(self, location=None):
        """
        List images available to the user provided credentials

        @inherits: :class:`NodeDriver.list_images`
        """
        res = self.connection.request("XMLRPCgetImages").object
        return [self._to_image(i) for i in res]

    def list_sizes(self, location=None):
        """
        VCL does not choosing sizes for node creation.
        Size of images are statically set by administrators.

        @inherits: :class:`NodeDriver.list_sizes`
        """
        return [NodeSize("t1.micro", "none", "512", 0, 0, 0, self)]

    def _to_connect_data(self, request_id, ipaddr):
        res = self._vcl_request("XMLRPCgetRequestConnectData", request_id, ipaddr)
        return res

    def _to_status(self, requestid, imagename, ipaddr):
        res = self._vcl_request("XMLRPCgetRequestStatus", requestid)

        public_ips = []
        extra = []
        if res["status"] == "ready":
            cdata = self._to_connect_data(requestid, ipaddr)
            public_ips = [cdata["serverIP"]]
            extra = {"user": cdata["user"], "pass": cdata["password"]}
        return Node(
            id=requestid,
            name=imagename,
            state=self.NODE_STATE_MAP[res["status"]],
            public_ips=public_ips,
            private_ips=[],
            driver=self,
            image=imagename,
            extra=extra,
        )

    def _to_nodes(self, res, ipaddr):
        return [self._to_status(h["requestid"], h["imagename"], ipaddr) for h in res]

    def list_nodes(self, ipaddr):
        """
        List nodes

        :param  ipaddr: IP address which should be used
        :type   ipaddr: ``str``

        :rtype: ``list`` of :class:`Node`
        """
        res = self._vcl_request("XMLRPCgetRequestIds")
        return self._to_nodes(res["requests"], ipaddr)

    def ex_update_node_access(self, node, ipaddr):
        """
        Update the remote ip accessing the node.

        :param node: the reservation node to update
        :type  node: :class:`Node`

        :param ipaddr: the ipaddr used to access the node
        :type  ipaddr: ``str``

        :return: node with updated information
        :rtype: :class:`Node`
        """
        return self._to_status(node.id, node.image, ipaddr)

    def ex_extend_request_time(self, node, minutes):
        """
        Time in minutes to extend the requested node's reservation time

        :param node: the reservation node to update
        :type  node: :class:`Node`

        :param minutes: the number of mintes to update
        :type  minutes: ``str``

        :return: true on success, throws error on failure
        :rtype: ``bool``
        """
        return self._vcl_request("XMLRPCextendRequest", node.id, minutes)

    def ex_get_request_end_time(self, node):
        """
        Get the ending time of the node reservation.

        :param node: the reservation node to update
        :type  node: :class:`Node`

        :return: unix timestamp
        :rtype: ``int``
        """
        res = self._vcl_request("XMLRPCgetRequestIds")
        time = 0
        for i in res["requests"]:
            if i["requestid"] == node.id:
                time = i["end"]
        return time
