# 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.    


from urllib.parse import urlparse, urlunparse
import rapidjson
import time
import re


from .exceptions import ValidationError


def serialize(data: dict) -> str:
    """! Serialize a dict into a JSON formatted string.

    This function enforces rules like the separator and order of keys.
    This ensures that all dicts are serialized in the same way.

    This is specially important for hashing data. We need to make sure that
    everyone serializes their data in the same way so that we do not have
    hash mismatches for the same structure due to serialization
    differences.

    @param data (dict): Data to serialize

    @return JSON formatted string

    """
    return rapidjson.dumps(data, skipkeys=False, ensure_ascii=False, sort_keys=True)


def gen_timestamp():
    """! The Unix time, rounded to the nearest second.
    See https://en.wikipedia.org/wiki/Unix_time
    @return The Unix time
    """
    return str(round(time.time()))


DEFAULT_NODE = "http://localhost:9984"


class CreateOperation:
    """! Class representing the ``'CREATE'`` transaction operation.
    """


class TransferOperation:
    """! Class representing the ``'TRANSFER'`` transaction operation.
    """


ops_map = {
    "CREATE": CreateOperation,
    "TRANSFER": TransferOperation,
}


def _normalize_operation(operation):
    """! Normalizes the given operation string. For now, this simply means
    converting the given string to uppercase, looking it up in
    :attr:`~.ops_map`, and returning the corresponding class if
    present.

    @param operation (str): The operation string to convert.

    @return The class corresponding to the given string,
            :class:`~.CreateOperation` or :class:`~TransferOperation`.

        .. important:: If the :meth:`str.upper` step, or the
            :attr:`~.ops_map` lookup fails, the given ``operation``
            argument is returned.

    """
    try:
        operation = operation.upper()
    except AttributeError:
        pass

    try:
        operation = ops_map[operation]()
    except KeyError:
        pass

    return operation


def _get_default_port(scheme):
    return 443 if scheme == "https" else 9984


def normalize_url(node):
    """! Normalizes the given node url"""
    if not node:
        node = DEFAULT_NODE
    elif "://" not in node:
        node = "//{}".format(node)
    parts = urlparse(node, scheme="http", allow_fragments=False)
    port = parts.port if parts.port else _get_default_port(parts.scheme)
    netloc = "{}:{}".format(parts.hostname, port)
    return urlunparse((parts.scheme, netloc, parts.path, "", "", ""))


def normalize_node(node, headers=None):
    """! Normalizes given node as str or dict with headers"""
    headers = {} if headers is None else headers
    if isinstance(node, str):
        url = normalize_url(node)
        return {"endpoint": url, "headers": headers}

    url = normalize_url(node["endpoint"])
    node_headers = node.get("headers", {})
    return {"endpoint": url, "headers": {**headers, **node_headers}}


def normalize_nodes(*nodes, headers=None):
    """! Normalizes given dict or array of driver nodes"""
    if not nodes:
        return (normalize_node(DEFAULT_NODE, headers),)

    normalized_nodes = ()
    for node in nodes:
        normalized_nodes += (normalize_node(node, headers),)
    return normalized_nodes
