#!/usr/bin/env python
#
# Copyright 2007 Google Inc.
#
# 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.
#




"""Implementation of the datastore_v4 API that forwards to the v3 service."""













from google.appengine.datastore import entity_pb

from google.appengine.api import api_base_pb
from google.appengine.api import apiproxy_stub
from google.appengine.api import apiproxy_stub_map
from google.appengine.datastore import datastore_pb
from google.appengine.datastore import datastore_pbs
from google.appengine.datastore import datastore_query
from google.appengine.datastore import datastore_stub_util
from google.appengine.datastore import datastore_v4_pb
from google.appengine.datastore import datastore_v4_validator
from google.appengine.runtime import apiproxy_errors


SERVICE_NAME = 'datastore_v4'
V3_SERVICE_NAME = 'datastore_v3'


class DatastoreV4Stub(apiproxy_stub.APIProxyStub):
  """Implementation of the datastore_v4 API that forwards to the v3 service."""


  THREADSAFE = False

  def __init__(self, app_id):
    apiproxy_stub.APIProxyStub.__init__(self, SERVICE_NAME)
    self.__app_id = app_id
    self.__entity_converter = datastore_pbs.get_entity_converter()
    self.__service_converter = datastore_stub_util.get_service_converter()
    self.__service_validator = datastore_v4_validator.get_service_validator()

  def _Dynamic_BeginTransaction(self, req, resp):
    try:
      self.__service_validator.validate_begin_transaction_req(req)
      v3_req = self.__service_converter.v4_to_v3_begin_transaction_req(
          self.__app_id, req)
      v3_resp = datastore_pb.Transaction()
      self.__make_v3_call('BeginTransaction', v3_req, v3_resp)
    except datastore_pbs.InvalidConversionError, e:
      raise apiproxy_errors.ApplicationError(
          datastore_v4_pb.Error.BAD_REQUEST, str(e))
    except datastore_v4_validator.ValidationError, e:
      raise apiproxy_errors.ApplicationError(
          datastore_v4_pb.Error.BAD_REQUEST, str(e))
    try:
      v4_resp = self.__service_converter.v3_to_v4_begin_transaction_resp(
          v3_resp)
    except datastore_pbs.InvalidConversionError, e:
      raise apiproxy_errors.ApplicationError(
          datastore_v4_pb.Error.INTERNAL_ERROR, str(e))
    resp.CopyFrom(v4_resp)

  def _Dynamic_Rollback(self, req, unused_resp):
    try:
      self.__service_validator.validate_rollback_req(req)
      v3_req = self.__service_converter.v4_rollback_req_to_v3_txn(req)
      self.__make_v3_call('Rollback', v3_req, api_base_pb.VoidProto())
    except datastore_pbs.InvalidConversionError, e:
      raise apiproxy_errors.ApplicationError(
          datastore_v4_pb.Error.BAD_REQUEST, str(e))
    except datastore_v4_validator.ValidationError, e:
      raise apiproxy_errors.ApplicationError(
          datastore_v4_pb.Error.BAD_REQUEST, str(e))

  def _Dynamic_Commit(self, req, resp):
    try:
      self.__service_validator.validate_commit_req(req)
      if req.has_transaction():
        resp.mutable_deprecated_mutation_result()
        resp.mutable_deprecated_mutation_result().CopyFrom(
            self.__apply_v4_deprecated_mutation(req.deprecated_mutation(),
                                                req.transaction()))
        v3_req = self.__service_converter.v4_commit_req_to_v3_txn(req)
        v3_resp = datastore_pb.CommitResponse()
        self.__make_v3_call('Commit', v3_req, v3_resp)
        total_index_updates = (
            resp.mutable_deprecated_mutation_result().index_updates()
            + v3_resp.cost().index_writes())
        resp.mutable_deprecated_mutation_result().set_index_updates(
            total_index_updates)
      else:
        resp.mutable_deprecated_mutation_result().CopyFrom(
            self.__apply_v4_deprecated_mutation(req.deprecated_mutation(),
                                                None))
    except datastore_pbs.InvalidConversionError, e:
      raise apiproxy_errors.ApplicationError(
          datastore_v4_pb.Error.BAD_REQUEST, str(e))
    except datastore_v4_validator.ValidationError, e:
      raise apiproxy_errors.ApplicationError(
          datastore_v4_pb.Error.BAD_REQUEST, str(e))

  def _GetQueryCompositeFilter(self, filters, operator):
    """Wraps the filters in a datastore_query.CompositeFilter if length > 1."""

    if not filters:
      return None
    elif len(filters) == 1:
      return filters[0]
    else:
      return datastore_query.CompositeFilter(operator, filters)

  def _GetV4PbCompositeFilter(self, filter_pbs, operator_pb):
    """Wraps the filters in a datastore_v4_pb.CompositeFilter if length > 1."""
    if not filter_pbs:
      return None
    elif len(filter_pbs) == 1:
      return filter_pbs[0]
    else:
      res_filter_pb = datastore_v4_pb.Filter()
      composite_filter_pb = res_filter_pb.mutable_composite_filter()
      composite_filter_pb.set_operator(operator_pb)
      composite_filter_pb.filter_list().extend(filter_pbs)
      return res_filter_pb

  def _GetFilterPbList(self, filter_pb):
    if filter_pb.has_composite_filter():
      composite_filter = filter_pb.composite_filter()
      assert composite_filter.operator() == datastore_v4_pb.CompositeFilter.AND

      return composite_filter.filter_list()
    else:
      return [filter_pb]

  def _ConvertGeospatialFilterOrNone(self, filter_pb):
    """Converts geo-spatial filters to filter predicates."""

    if filter_pb.has_bounding_circle_filter():
      return (datastore_query._BoundingCircleFilter._from_v4_pb(
          filter_pb.bounding_circle_filter()))
    elif filter_pb.has_bounding_box_filter():
      return (datastore_query._BoundingBoxFilter._from_v4_pb(
          filter_pb.bounding_box_filter()))
    else:
      return None

  def _SplitGeospatialFilters(self, req):
    """Extracts, converts and removes geo-filters from a request.

    Args:
      req: a datastore_v4_pb.RunQueryRequest

    Returns:
      a pair (new_req, filter_predicate) where new_req is req with unsupported
      filters removed and filter_predicate is a datastore_query.FilterPredicate
      with the unsupported filters. filter_predicate is None if no unsupported
      filters were removed.
    """

    assert datastore_v4_pb.CompositeFilter._Operator_NAMES.values() == ['AND']



    filter_predicate = None
    new_req = datastore_v4_pb.RunQueryRequest()
    new_req.CopyFrom(req)

    query = new_req.mutable_query()


    sub_filter_pbs = []
    sub_filter_predicates = []

    for filter_pb in self._GetFilterPbList(req.query().filter()):
      sub_filter_predicate = self._ConvertGeospatialFilterOrNone(filter_pb)

      if sub_filter_predicate is None:
        sub_filter_pbs.append(filter_pb)
      else:
        sub_filter_predicates.append(sub_filter_predicate)

    op_pb = datastore_v4_pb.CompositeFilter.AND
    op = datastore_query.CompositeFilter.AND

    filter_pb = self._GetV4PbCompositeFilter(sub_filter_pbs, op_pb)
    filter_predicate = self._GetQueryCompositeFilter(sub_filter_predicates, op)



    if filter_pb is None:
      query.clear_filter()
    else:
      query.mutable_filter().CopyFrom(filter_pb)

    return (new_req, filter_predicate)

  def _Dynamic_RunQuery(self, req, resp):
    try:
      self.__normalize_v4_run_query_request(req)
      self.__service_validator.validate_run_query_req(req)

      v3_stub = apiproxy_stub_map.apiproxy.GetStub(V3_SERVICE_NAME)

      new_req, filter_predicate = self._SplitGeospatialFilters(req)




      if (issubclass(v3_stub.__class__, datastore_stub_util.BaseDatastore)
          and filter_predicate is not None):
        v3_req = self.__service_converter.v4_run_query_req_to_v3_query(new_req)
        v3_resp = datastore_pb.QueryResult()
        v3_stub._Dynamic_RunQuery(v3_req, v3_resp, filter_predicate)
      else:
        v3_req = self.__service_converter.v4_run_query_req_to_v3_query(req)
        v3_resp = datastore_pb.QueryResult()
        self.__make_v3_call('RunQuery', v3_req, v3_resp)
    except datastore_pbs.InvalidConversionError, e:
      raise apiproxy_errors.ApplicationError(
          datastore_v4_pb.Error.BAD_REQUEST, str(e))
    except datastore_v4_validator.ValidationError, e:
      raise apiproxy_errors.ApplicationError(
          datastore_v4_pb.Error.BAD_REQUEST, str(e))
    try:
      v4_resp = self.__service_converter.v3_to_v4_run_query_resp(v3_resp)
      if req.query().projection_list():
        if req.query().projection_list() == ['__key__']:
          result_type = datastore_v4_pb.EntityResult.KEY_ONLY
        else:
          result_type = datastore_v4_pb.EntityResult.PROJECTION
        v4_resp.mutable_batch().set_entity_result_type(result_type)
    except datastore_pbs.InvalidConversionError, e:
      raise apiproxy_errors.ApplicationError(
          datastore_v4_pb.Error.INTERNAL_ERROR, str(e))
    resp.CopyFrom(v4_resp)

  def _Dynamic_ContinueQuery(self, req, resp):
    try:
      self.__service_validator.validate_continue_query_req(req)
      v3_req = self.__service_converter.v4_to_v3_next_req(req)
      v3_resp = datastore_pb.QueryResult()
      self.__make_v3_call('Next', v3_req, v3_resp)
    except datastore_pbs.InvalidConversionError, e:
      raise apiproxy_errors.ApplicationError(
          datastore_v4_pb.Error.BAD_REQUEST, str(e))
    except datastore_v4_validator.ValidationError, e:
      raise apiproxy_errors.ApplicationError(
          datastore_v4_pb.Error.BAD_REQUEST, str(e))
    try:
      v4_resp = self.__service_converter.v3_to_v4_continue_query_resp(v3_resp)
    except datastore_pbs.InvalidConversionError, e:
      raise apiproxy_errors.ApplicationError(
          datastore_v4_pb.Error.INTERNAL_ERROR, str(e))
    resp.CopyFrom(v4_resp)

  def _Dynamic_Lookup(self, req, resp):
    try:
      self.__service_validator.validate_lookup_req(req)
      v3_req = self.__service_converter.v4_to_v3_get_req(req)
      v3_resp = datastore_pb.GetResponse()
      self.__make_v3_call('Get', v3_req, v3_resp)
    except datastore_pbs.InvalidConversionError, e:
      raise apiproxy_errors.ApplicationError(
          datastore_v4_pb.Error.BAD_REQUEST, str(e))
    except datastore_v4_validator.ValidationError, e:
      raise apiproxy_errors.ApplicationError(
          datastore_v4_pb.Error.BAD_REQUEST, str(e))
    try:
      v4_resp = self.__service_converter.v3_to_v4_lookup_resp(v3_resp)
    except datastore_pbs.InvalidConversionError, e:
      raise apiproxy_errors.ApplicationError(
          datastore_v4_pb.Error.INTERNAL_ERROR, str(e))
    resp.CopyFrom(v4_resp)

  def _Dynamic_AllocateIds(self, req, resp):



    v3_stub = apiproxy_stub_map.apiproxy.GetStub(V3_SERVICE_NAME)
    try:
      self.__service_validator.validate_allocate_ids_req(req)

      if req.allocate_list():
        v3_refs = self.__entity_converter.v4_to_v3_references(
            req.allocate_list())

        v3_full_refs = v3_stub._AllocateIds(v3_refs)
        resp.allocated_list().extend(
            self.__entity_converter.v3_to_v4_keys(v3_full_refs))
      elif req.reserve_list():
        v3_refs = self.__entity_converter.v4_to_v3_references(
            req.reserve_list())

        v3_stub._AllocateIds(v3_refs)
    except datastore_pbs.InvalidConversionError, e:
      raise apiproxy_errors.ApplicationError(
          datastore_v4_pb.Error.BAD_REQUEST, str(e))
    except datastore_v4_validator.ValidationError, e:
      raise apiproxy_errors.ApplicationError(
          datastore_v4_pb.Error.BAD_REQUEST, str(e))

  def __insert_v3_entity(self, v3_entity, v3_txn):
    """Inserts a v3 entity.

    Args:
      v3_entity: a datastore_v4_pb.Entity
      v3_txn: a datastore_pb.Transaction or None

    Returns:
      the number of index writes that occurred

    Raises:
      ApplicationError: if the entity already exists
    """
    if not v3_txn:

      v3_txn = datastore_pb.Transaction()
      v3_begin_txn_req = datastore_pb.BeginTransactionRequest()
      v3_begin_txn_req.set_app(v3_entity.key().app())
      self.__make_v3_call('BeginTransaction', v3_begin_txn_req, v3_txn)
      self.__insert_v3_entity(v3_entity, v3_txn)
      v3_resp = datastore_pb.CommitResponse()
      self.__make_v3_call('Commit', v3_txn, v3_resp)
      return v3_resp.cost().index_writes()

    v3_get_req = datastore_pb.GetRequest()
    v3_get_req.mutable_transaction().CopyFrom(v3_txn)
    v3_get_req.key_list().append(v3_entity.key())
    v3_get_resp = datastore_pb.GetResponse()
    self.__make_v3_call('Get', v3_get_req, v3_get_resp)
    if v3_get_resp.entity(0).has_entity():
      raise apiproxy_errors.ApplicationError(datastore_v4_pb.Error.BAD_REQUEST,
                                             'Entity already exists.')
    v3_put_req = datastore_pb.PutRequest()
    v3_put_req.mutable_transaction().CopyFrom(v3_txn)
    v3_put_req.entity_list().append(v3_entity)
    v3_put_resp = datastore_pb.PutResponse()
    self.__make_v3_call('Put', v3_put_req, v3_put_resp)
    return v3_put_resp.cost().index_writes()

  def __update_v3_entity(self, v3_entity, v3_txn):
    """Updates a v3 entity.

    Args:
      v3_entity: a datastore_v4_pb.Entity
      v3_txn: a datastore_pb.Transaction or None

    Returns:
      the number of index writes that occurred

    Raises:
      ApplicationError: if the entity does not exist
    """
    if not v3_txn:

      v3_txn = datastore_pb.Transaction()
      v3_begin_txn_req = datastore_pb.BeginTransactionRequest()
      v3_begin_txn_req.set_app(v3_entity.key().app())
      self.__make_v3_call('BeginTransaction', v3_begin_txn_req, v3_txn)
      self.__update_v3_entity(v3_entity, v3_txn)
      v3_resp = datastore_pb.CommitResponse()
      self.__make_v3_call('Commit', v3_txn, v3_resp)
      return v3_resp.cost().index_writes()

    v3_get_req = datastore_pb.GetRequest()
    v3_get_req.mutable_transaction().CopyFrom(v3_txn)
    v3_get_req.key_list().append(v3_entity.key())
    v3_get_resp = datastore_pb.GetResponse()
    self.__make_v3_call('Get', v3_get_req, v3_get_resp)
    if not v3_get_resp.entity(0).has_entity():
      raise apiproxy_errors.ApplicationError(datastore_v4_pb.Error.BAD_REQUEST,
                                             'Entity does not exist.')
    v3_put_req = datastore_pb.PutRequest()
    v3_put_req.mutable_transaction().CopyFrom(v3_txn)
    v3_put_req.entity_list().append(v3_entity)
    v3_put_resp = datastore_pb.PutResponse()
    self.__make_v3_call('Put', v3_put_req, v3_put_resp)
    return v3_put_resp.cost().index_writes()

  def __apply_v4_deprecated_mutation(self, v4_deprecated_mutation, v4_txn):
    """Applies a v4 DeprecatedMutation.

    Args:
      v4_deprecated_mutation: a datastore_v4_pb.DeprecatedMutation
      v4_txn: an optional v4 transaction handle or None

    Returns:
      a datastore_v4_pb.DeprecatedMutationResult
    """
    index_writes = 0
    v3_txn = None
    if v4_txn:
      v3_txn = datastore_pb.Transaction()
      self.__service_converter.v4_to_v3_txn(v4_txn, v3_txn)


    for v4_entity in v4_deprecated_mutation.insert_list():
      v3_entity = entity_pb.EntityProto()
      self.__entity_converter.v4_to_v3_entity(v4_entity, v3_entity)
      index_writes += self.__insert_v3_entity(v3_entity, v3_txn)


    for v4_entity in v4_deprecated_mutation.update_list():
      v3_entity = entity_pb.EntityProto()
      self.__entity_converter.v4_to_v3_entity(v4_entity, v3_entity)
      index_writes += self.__update_v3_entity(v3_entity, v3_txn)


    v3_insert_auto_req = datastore_pb.PutRequest()
    if v3_txn:
      v3_insert_auto_req.mutable_transaction().CopyFrom(v3_txn)
    for v4_entity in v4_deprecated_mutation.insert_auto_id_list():
      v3_entity = entity_pb.EntityProto()
      self.__entity_converter.v4_to_v3_entity(v4_entity, v3_entity)
      v3_insert_auto_req.entity_list().append(v3_entity)
    v3_insert_auto_id_resp = datastore_pb.PutResponse()
    self.__make_v3_call('Put', v3_insert_auto_req, v3_insert_auto_id_resp)
    index_writes += v3_insert_auto_id_resp.cost().index_writes()


    v3_upsert_req = datastore_pb.PutRequest()
    if v3_txn:
      v3_upsert_req.mutable_transaction().CopyFrom(v3_txn)
    for v4_entity in v4_deprecated_mutation.upsert_list():
      v3_entity = entity_pb.EntityProto()
      self.__entity_converter.v4_to_v3_entity(v4_entity, v3_entity)
      v3_upsert_req.entity_list().append(v3_entity)
    v3_upsert_resp = datastore_pb.PutResponse()
    self.__make_v3_call('Put', v3_upsert_req, v3_upsert_resp)
    index_writes += v3_upsert_resp.cost().index_writes()


    v3_delete_req = datastore_pb.DeleteRequest()
    if v3_txn:
      v3_delete_req.mutable_transaction().CopyFrom(v3_txn)
    for v4_key in v4_deprecated_mutation.delete_list():
      self.__entity_converter.v4_to_v3_reference(v4_key,
                                                 v3_delete_req.add_key())
    v3_delete_resp = datastore_pb.DeleteResponse()
    self.__make_v3_call('Delete', v3_delete_req, v3_delete_resp)
    index_writes += v3_delete_resp.cost().index_writes()

    v4_deprecated_mutation_result = datastore_v4_pb.DeprecatedMutationResult()
    for v3_ref in v3_insert_auto_id_resp.key_list():
      self.__entity_converter.v3_to_v4_key(
          v3_ref, v4_deprecated_mutation_result.add_insert_auto_id_key())
    v4_deprecated_mutation_result.set_index_updates(index_writes)

    return v4_deprecated_mutation_result

  def __normalize_v4_run_query_request(self, v4_req):

    pass

  def __make_v3_call(self, method, v3_req, v3_resp):
    apiproxy_stub_map.MakeSyncCall(V3_SERVICE_NAME, method, v3_req, v3_resp)
