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

import six
from six.moves import map

from grakn.service.Session.util.RequestBuilder import RequestBuilder
import grakn.service.Session.util.ResponseReader as ResponseReader # for circular import issue
from grakn.service.Session.util import enums
from grakn.service.Session.util.Communicator import Communicator
from grakn.exception.GraknError import GraknError


class TransactionService(object):

    def __init__(self, session_id, tx_type, transaction_endpoint):
        self.session_id = session_id
        self.tx_type = tx_type.value

        self._communicator = Communicator(transaction_endpoint)

        # open the transaction with an 'open' message
        open_req = RequestBuilder.open_tx(session_id, tx_type)
        self._communicator.single_request(open_req)
    __init__.__annotations__ = {'tx_type': enums.TxType}

    # --- Passthrough targets ---
    # targets of top level Transaction class

    def query(self, query, infer, explain, batch_size):
        return Iterator(self._communicator,
                        RequestBuilder.start_iterating_query(query, infer, explain, batch_size),
                        ResponseReader.ResponseReader.get_query_results(self))
    query.__annotations__ = {'query': str}

    def commit(self):
        request = RequestBuilder.commit()
        self._communicator.single_request(request)

    def close(self):
        self._communicator.close()

    def is_closed(self):
        return self._communicator._closed

    def get_concept(self, concept_id):
        request = RequestBuilder.get_concept(concept_id)
        response = self._communicator.single_request(request)
        return ResponseReader.ResponseReader.get_concept(self, response.getConcept_res)
    get_concept.__annotations__ = {'concept_id': str}

    def get_schema_concept(self, label):
        request = RequestBuilder.get_schema_concept(label)
        response = self._communicator.single_request(request)
        return ResponseReader.ResponseReader.get_schema_concept(self, response.getSchemaConcept_res)
    get_schema_concept.__annotations__ = {'label': str}

    def get_attributes_by_value(self, attribute_value, value_type):
        request = RequestBuilder.start_iterating_get_attributes_by_value(attribute_value, value_type)
        return Iterator(self._communicator, request, ResponseReader.ResponseReader.get_attributes_by_value(self))
    get_attributes_by_value.__annotations__ = {'data_type': enums.ValueType}

    def put_entity_type(self, label):
        request = RequestBuilder.put_entity_type(label)
        response = self._communicator.single_request(request)
        return ResponseReader.ResponseReader.put_entity_type(self, response.putEntityType_res)
    put_entity_type.__annotations__ = {'label': str}

    def put_relation_type(self, label):
        request = RequestBuilder.put_relation_type(label)
        response = self._communicator.single_request(request)
        return ResponseReader.ResponseReader.put_relation_type(self, response.putRelationType_res)
    put_relation_type.__annotations__ = {'label': str}

    def put_attribute_type(self, label, value_type):
        request = RequestBuilder.put_attribute_type(label, value_type)
        response = self._communicator.single_request(request)
        return ResponseReader.ResponseReader.put_attribute_type(self, response.putAttributeType_res)
    put_attribute_type.__annotations__ = {'label': str, 'value_type': enums.ValueType}

    def put_role(self, label):
        request = RequestBuilder.put_role(label)
        response = self._communicator.single_request(request)
        return ResponseReader.ResponseReader.put_role(self, response.putRole_res)
    put_role.__annotations__ = {'label': str}

    def put_rule(self, label, when, then):
        request = RequestBuilder.put_rule(label, when, then)
        response = self._communicator.single_request(request)
        return ResponseReader.ResponseReader.put_rule(self, response.putRule_res)
    put_rule.__annotations__ = {'label': str, 'when': str, 'then': str}

    # --- Transaction Messages ---

    def run_concept_method(self, concept_id, grpc_concept_method_req):
        # wrap method_req into a transaction message
        tx_request = RequestBuilder.concept_method_req_to_tx_req(concept_id, grpc_concept_method_req)
        response = self._communicator.single_request(tx_request)
        return response.conceptMethod_res.response

    def run_concept_iter_method(self, concept_id, grpc_concept_iter_method_req):
        return Iterator(self._communicator,
                        RequestBuilder.start_iterating_concept_method(concept_id, grpc_concept_iter_method_req),
                        lambda res: res.conceptMethod_iter_res.response)

    def explanation(self, explainable):
        """ Retrieve the explanation of a Concept Map from the server """
        tx_request = RequestBuilder.explanation(explainable)
        response = self._communicator.single_request(tx_request)
        return ResponseReader.ResponseReader.create_explanation(self, response.explanation_res)


end_of_batch_results = {'done', 'iteratorId'}


def end_of_batch(res):
    res_type = res.iter_res.WhichOneof('res')
    return res_type == 'done' or res_type == 'iteratorId'


class Iterator(six.Iterator):
    def __init__(self, communicator, iter_req, resp_converter):
        self._communicator = communicator
        self._iter_req = iter_req
        self._response_iterator = self._communicator.iteration_request(
            RequestBuilder.iter_req_to_tx_req(self._iter_req),
            end_of_batch)
        self._done = False
        self._resp_converter = resp_converter

    def __iter__(self):
        return self

    def _request_next_batch(self, iter_id):
        self._response_iterator = self._communicator.iteration_request(
            RequestBuilder.iter_req_to_tx_req(
                RequestBuilder.continue_iterating(iter_id, self._iter_req.options)),
            end_of_batch)

    def __next__(self):
        if self._done:
            raise GraknError('Iterator was already iterated.')

        try:
            response = next(self._response_iterator)
        except StopIteration:
            raise GraknError('Internal client/protocol error,'
                             ' did not receive an expected "done" or "iteratorId" message.'
                             '\n\n Please ensure client version is supported by server version.')

        iter_res = response.iter_res
        res_type = iter_res.WhichOneof('res')
        if res_type == 'done':
            self._done = True
            raise StopIteration
        elif res_type == 'iteratorId':
            self._request_next_batch(iter_res.iteratorId)
            return next(self)
        else:
            return self._resp_converter(iter_res)

    def get(self):
        if not self._done:
            self._response_iterator.get()
        return self
