#
# 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 gremlin_python.driver.connection import GremlinServerError
from gremlin_python.process.traversal import Traverser
from gremlin_python.structure.io import graphbinaryV4
from gremlin_python.structure.io.util import Marker

__author__ = 'David M. Brown (davebshow@gmail.com), Lyndon Bauto (lyndonb@bitquilltech.com)'

"""
GraphBinaryV4
"""
class GraphBinarySerializersV4(object):
    DEFAULT_READER_CLASS = graphbinaryV4.GraphBinaryReader
    DEFAULT_WRITER_CLASS = graphbinaryV4.GraphBinaryWriter
    DEFAULT_VERSION = b"application/vnd.graphbinary-v4.0"

    int_pack = graphbinaryV4.int32_pack

    def __init__(self, reader=None, writer=None, version=None, pdt_registry=None):
        if not version:
            version = self.DEFAULT_VERSION
        self._version = version
        if not reader:
            reader = self.DEFAULT_READER_CLASS(pdt_registry=pdt_registry)
        self._graphbinary_reader = reader
        if not writer:
            writer = self.DEFAULT_WRITER_CLASS()
        self._graphbinary_writer = writer

    def configure_pdt_registry(self, pdt_registry):
        if self._graphbinary_reader.pdt_registry is None:
            self._graphbinary_reader.pdt_registry = pdt_registry
        else:
            self._graphbinary_reader.pdt_registry._composite_adapters_by_name.update(pdt_registry._composite_adapters_by_name)
            self._graphbinary_reader.pdt_registry._composite_adapters_by_class.update(pdt_registry._composite_adapters_by_class)
            self._graphbinary_reader.pdt_registry._primitive_adapters_by_name.update(pdt_registry._primitive_adapters_by_name)
            self._graphbinary_reader.pdt_registry._primitive_adapters_by_class.update(pdt_registry._primitive_adapters_by_class)

    @property
    def version(self):
        """Read only property"""
        return self._version

    def serialize_message(self, request_message):
        message = self.build_message(request_message.fields, request_message.gremlin)
        return message

    def build_message(self, fields, gremlin):
        message = {
            'fields': fields,
            'gremlin': gremlin
        }
        return self.finalize_message(message)

    def finalize_message(self, message):
        ba = bytearray()

        ba.extend(graphbinaryV4.uint8_pack(0x84))
        fields = message["fields"]
        ba.extend(self.int_pack(len(fields)))
        for k, v in fields.items():
            self._graphbinary_writer.to_dict(k, ba)
            self._graphbinary_writer.to_dict(v, ba)

        gremlin = message['gremlin']
        # TODO: hack to remove type code from gremlin value for V4 message format, writer doesn't seem to have a way to
        #  write value directly by passing serializer types, check back when removing bytecode
        gremlin_ba = bytearray()
        self._graphbinary_writer.to_dict(gremlin, gremlin_ba)
        ba.extend(gremlin_ba[2:])

        return bytes(ba)

    def deserialize_response_stream(self, stream):
        """
        Yield each result item from a GraphBinary response stream as it is read
        off the wire, then raise ``GremlinServerError`` if the trailing status
        is not a success code. ``Connection._receive`` dispatches through this
        method so custom response serializers can replace GraphBinary parsing.
        """
        reader = self._graphbinary_reader

        # Response header: version byte + flags byte
        stream.read(1)
        flags = stream.read(1)[0]
        bulked = flags == 0x01

        while True:
            obj = reader.to_object(stream)
            if obj == Marker.end_of_stream():
                break
            if bulked:
                bulk = reader.to_object(stream)
                yield Traverser(obj, bulk)
            else:
                yield obj

        # Trailer: status code, message, exception
        status_code = graphbinaryV4.int32_unpack(stream.read(4))
        msg_is_null = stream.read(1)[0] == 0x01
        status_message = '' if msg_is_null else reader.to_object(
            stream, graphbinaryV4.DataType.string, False)
        exc_is_null = stream.read(1)[0] == 0x01
        status_exception = '' if exc_is_null else reader.to_object(
            stream, graphbinaryV4.DataType.string, False)

        if status_code not in (200, 204):
            raise GremlinServerError({
                'code': status_code,
                'message': status_message,
                'exception': status_exception
            })
