/**
 * inf_adopted_operation_transform:
 * @operation: The #InfAdoptedOperation to transform.
 * @against: The operation to transform against.
 * @concurrency_id: The concurrency ID for the transformation.
 *
 * Performs an inclusion transformation of @operation against @against,
 * meaning that the effect of @against is included in @operation.
 *
 * If inf_adopted_operation_need_concurrency_id() returns %TRUE for @operation
 * and @against, then @concurrency_id must not be
 * %INF_ADOPTED_CONCURRENCY_NONE. Otherwise, the parameter is ignored.
 *
 * Return Value: The transformed #InfAdoptedOperation, or %NULL if the
 * transformation failed.
 **/
InfAdoptedOperation*
inf_adopted_operation_transform(InfAdoptedOperation* operation,
                                InfAdoptedOperation* against,
                                InfAdoptedConcurrencyId concurrency_id)
{
  InfAdoptedOperationIface* iface;

  g_return_val_if_fail(INF_ADOPTED_IS_OPERATION(operation), NULL);
  g_return_val_if_fail(INF_ADOPTED_IS_OPERATION(against), NULL);

  /* Transform against both parts of split operation if we are transforming
   * against split operation. */
  if(INF_ADOPTED_IS_SPLIT_OPERATION(against))
  {
    return inf_adopted_split_operation_transform_other(
      INF_ADOPTED_SPLIT_OPERATION(against),
      operation,
      concurrency_id
    );
  }
  else
  {
    iface = INF_ADOPTED_OPERATION_GET_IFACE(operation);
    g_assert(iface->transform != NULL);

    return (*iface->transform)(operation, against, concurrency_id);
  }
}
/**
 * inf_adopted_operation_get_concurrency_id:
 * @operation: The #InfAdoptedOperation to transform.
 * @against: The operation to transform against.
 *
 * This function returns a concurrency ID for transformation of @operation
 * against @against. It always returns %INF_ADOPTED_CONCURRENCY_NONE when
 * inf_adopted_operation_need_concurrency_id() returns %TRUE for
 * @operation and @against (but that's not necessarily true the other way
 * around), since it is not possible to decide which operation to transform
 * without any additional information.
 *
 * However, the function can be called on the same operations in a previous
 * state. In some cases, a decision can be made based on those previous
 * operations. This can then be used as concurrency ID to call
 * inf_adopted_operation_transform().
 *
 * Note that the function is antisymmetric. If it returns
 * %INF_ADOPTED_CONCURRENCY_SELF, then it returns
 * %INF_ADOPTED_CONCURRENCY_OTHER for swapped arguments.
 *
 * Returns: A concurrency ID between @operation and @against. Can be
 * %INF_ADOPTED_CONCURRENCY_NONE in case no decision can be made.
 */
InfAdoptedConcurrencyId
inf_adopted_operation_get_concurrency_id(InfAdoptedOperation* operation,
                                         InfAdoptedOperation* against)
{
  InfAdoptedOperationIface* iface;
  InfAdoptedConcurrencyId id;

  g_return_val_if_fail(
    INF_ADOPTED_IS_OPERATION(operation),
    INF_ADOPTED_CONCURRENCY_NONE
  );
  g_return_val_if_fail(
    INF_ADOPTED_IS_OPERATION(against),
    INF_ADOPTED_CONCURRENCY_NONE
  );

  /* Use antisymmetricity if second argument is split operation, so that
   * subclasses don't need to handle that case explicitely. */
  if(!INF_ADOPTED_IS_SPLIT_OPERATION(operation) &&
     INF_ADOPTED_IS_SPLIT_OPERATION(against))
  {
    iface = INF_ADOPTED_OPERATION_GET_IFACE(against);
    g_assert(iface->get_concurrency_id != NULL);

    id = iface->get_concurrency_id(against, operation);
    switch(id)
    {
    case INF_ADOPTED_CONCURRENCY_SELF:
      return INF_ADOPTED_CONCURRENCY_OTHER;
    case INF_ADOPTED_CONCURRENCY_NONE:
      return INF_ADOPTED_CONCURRENCY_NONE;
    case INF_ADOPTED_CONCURRENCY_OTHER:
      return INF_ADOPTED_CONCURRENCY_SELF;
    default:
      g_assert_not_reached();
      return INF_ADOPTED_CONCURRENCY_NONE;
    }
  }
  else
  {
    iface = INF_ADOPTED_OPERATION_GET_IFACE(operation);
    g_assert(iface->get_concurrency_id != NULL);

    return iface->get_concurrency_id(operation, against);
  }
}
static InfAdoptedOperation*
inf_text_remote_delete_operation_apply_transformed(InfAdoptedOperation* op,
                                                   InfAdoptedOperation* trans,
                                                   InfAdoptedUser* by,
                                                   InfBuffer* buffer,
                                                   GError** error)
{
  InfTextRemoteDeleteOperationPrivate* priv;
  InfTextChunk* chunk;
  InfTextChunk* temp_slice;
  GSList* list;
  GSList* item;
  InfAdoptedOperation* operation;
  GSList* recon_list;
  GSList* recon_item;
  InfTextRemoteDeleteOperationRecon* recon;
  InfTextDefaultDeleteOperation* result;

  g_assert(INF_TEXT_IS_REMOTE_DELETE_OPERATION(op));
  g_assert(INF_TEXT_IS_BUFFER(buffer));

  /* TODO: We can probably optimize this function, but then we should
   * a) profile it and b) in many cases input parameters to this function
   * are trivial anyway. */
  if(INF_ADOPTED_IS_SPLIT_OPERATION(trans))
  {
    list = inf_adopted_split_operation_unsplit(
      INF_ADOPTED_SPLIT_OPERATION(trans)
    );
  }
  else
  {
    list = g_slist_prepend(NULL, trans);
  }

  chunk = inf_text_chunk_new(
    inf_text_buffer_get_encoding(INF_TEXT_BUFFER(buffer))
  );

  /* We assume the list of remote delete operations to be in order */
  for(item = list; item != NULL; item = g_slist_next(item))
  {
    g_assert(INF_TEXT_IS_REMOTE_DELETE_OPERATION(item->data));
    priv = INF_TEXT_REMOTE_DELETE_OPERATION_PRIVATE(item->data);

    operation = INF_ADOPTED_OPERATION(item->data);

    if(priv->length > 0)
    {
      temp_slice = inf_text_buffer_get_slice(
        INF_TEXT_BUFFER(buffer),
        priv->position,
        priv->length
      );

      recon_list = inf_text_remote_delete_operation_recon_feed(
        priv->recon,
        0,
        temp_slice
      );

      inf_text_chunk_free(temp_slice);
    }
    else
    {
      recon_list = priv->recon;
    }

    for(recon_item = recon_list;
        recon_item != NULL;
        recon_item = g_slist_next(recon_item))
    {
      recon = (InfTextRemoteDeleteOperationRecon*)recon_item->data;
      g_assert(priv->recon_offset + recon->position ==
               inf_text_chunk_get_length(chunk));

      inf_text_chunk_insert_chunk(
        chunk,
        inf_text_chunk_get_length(chunk),
        recon->chunk
      );
    }

    /* Free recon list if newly allocated */
    if(priv->length > 0)
      inf_text_remote_delete_operation_recon_free(recon_list);

    if(!inf_adopted_operation_apply(operation, by, buffer, error))
    {
      g_slist_free(list);
      inf_text_chunk_free(chunk);
      return NULL;
    }
  }

  g_slist_free(list);

  priv = INF_TEXT_REMOTE_DELETE_OPERATION_PRIVATE(op);
  result = inf_text_default_delete_operation_new(priv->position, chunk);
  inf_text_chunk_free(chunk);

  return INF_ADOPTED_OPERATION(result);
}