/**
 * inf_adopted_session_write_request_info:
 * @session: A #InfAdoptedSession.
 * @diff_vec: A reference state vector, or %NULL.
 * @request: The #InfAdoptedRequest whose info to write.
 * @xml: The XML node to write the data into.
 * @operation: An XML node representing the operation of the request, or
 * %NULL.
 *
 * This function writes common data from @request, such as the user that
 * issued the request and the state in which the request was made into @xml.
 * If @diff_vec is given, then the state is written as a diff to this vector,
 * see inf_adopted_state_vector_to_string_diff(). Deserializing this data
 * again (via inf_adopted_session_read_request_info()) requires the same
 * @diff_vec then.
 *
 * This function is most likely to be used by implementations of the
 * request_to_xml virtual function.
 */
void
inf_adopted_session_write_request_info(InfAdoptedSession* session,
                                       InfAdoptedRequest* request,
                                       InfAdoptedStateVector* diff_vec,
                                       xmlNodePtr xml,
                                       xmlNodePtr operation)
{
  InfAdoptedStateVector* vector;
  guint user_id;
  gchar* vec_str;

  vector = inf_adopted_request_get_vector(request);
  user_id = inf_adopted_request_get_user_id(request);

  inf_xml_util_set_attribute_uint(xml, "user", user_id);

  if(diff_vec == NULL)
    vec_str = inf_adopted_state_vector_to_string(vector);
  else
    vec_str = inf_adopted_state_vector_to_string_diff(vector, diff_vec);

  inf_xml_util_set_attribute(xml, "time", vec_str);
  g_free(vec_str);
  
  if(operation != NULL)
    xmlAddChild(xml, operation);
}
/* Breadcasts a request N times - makes only sense for undo and redo requests,
 * so that's the only thing we offer API for. */
static void
inf_adopted_session_broadcast_n_requests(InfAdoptedSession* session,
                                         InfAdoptedRequest* request,
                                         guint n)
{
  InfAdoptedSessionPrivate* priv;
  InfAdoptedSessionClass* session_class;
  InfUserTable* user_table;
  guint user_id;
  InfUser* user;
  InfAdoptedSessionLocalUser* local;
  xmlNodePtr xml;

  priv = INF_ADOPTED_SESSION_PRIVATE(session);
  session_class = INF_ADOPTED_SESSION_GET_CLASS(session);
  g_assert(session_class->request_to_xml != NULL);

  user_table = inf_session_get_user_table(INF_SESSION(session));
  user_id = inf_adopted_request_get_user_id(request);
  user = inf_user_table_lookup_user_by_id(user_table, user_id);
  g_assert(user != NULL);

  local = inf_adopted_session_lookup_local_user(
    session,
    INF_ADOPTED_USER(user)
  );
  g_assert(local != NULL);

  xml = xmlNewNode(NULL, (const xmlChar*)"request");

  session_class->request_to_xml(
    session,
    xml,
    request,
    local->last_send_vector,
    FALSE
  );

  if(n > 1) inf_xml_util_set_attribute_uint(xml, "num", n);
  inf_session_send_to_subscriptions(INF_SESSION(session), xml);

  inf_adopted_state_vector_free(local->last_send_vector);
  local->last_send_vector = inf_adopted_state_vector_copy(
    inf_adopted_request_get_vector(request)
  );

  /* Add this request to last send vector if it increases vector time
   * (-> affects buffer). */
  if(inf_adopted_request_affects_buffer(request) == TRUE)
    inf_adopted_state_vector_add(local->last_send_vector, user_id, n);

  inf_adopted_session_stop_noop_timer(session, local);
}
static InfCommunicationScope
inf_adopted_session_process_xml_run(InfSession* session,
                                    InfXmlConnection* connection,
                                    const xmlNodePtr xml,
                                    GError** error)
{
  InfAdoptedSessionPrivate* priv;
  InfAdoptedSessionClass* session_class;
  InfAdoptedRequest* request;
  InfAdoptedUser* user;

  gboolean has_num;
  guint num;
  GError* local_error;
  InfAdoptedRequest* copy_req;
  guint i;

  priv = INF_ADOPTED_SESSION_PRIVATE(session);

  if(strcmp((const char*)xml->name, "request") == 0)
  {
    session_class = INF_ADOPTED_SESSION_GET_CLASS(session);
    g_assert(session_class->xml_to_request != NULL);

    user = inf_adopted_session_user_from_request_xml(
      INF_ADOPTED_SESSION(session),
      xml,
      error
    );

    if(user == NULL)
      return INF_COMMUNICATION_SCOPE_PTP;

    if(inf_user_get_status(INF_USER(user)) == INF_USER_UNAVAILABLE ||
       inf_user_get_connection(INF_USER(user)) != connection)
    {
      g_set_error(
        error,
        inf_user_error_quark(),
        INF_USER_ERROR_NOT_JOINED,
        "%s",
        _("User did not join from this connection")
      );

      return INF_COMMUNICATION_SCOPE_PTP;
    }

    local_error = NULL;
    has_num = inf_xml_util_get_attribute_uint(xml, "num", &num, &local_error);
    if(local_error != NULL)
    {
      g_propagate_error(error, local_error);
      return INF_COMMUNICATION_SCOPE_PTP;
    }
    
    request = session_class->xml_to_request(
      INF_ADOPTED_SESSION(session),
      xml,
      inf_adopted_user_get_vector(user),
      FALSE,
      error
    );

    if(request == NULL)
      return INF_COMMUNICATION_SCOPE_PTP;

    inf_adopted_algorithm_receive_request(priv->algorithm, request);

    /* Apply the request more than once if num is given. This is mostly used
     * for multiple undos and redos, but is in general allowed for any
     * request. */
    if(has_num)
    {
      for(i = 1; i < num; ++i)
      {
        /* TODO: This is a bit of a hack since requests are normally
         * immutable. It avoids an additional vector copy here though. */
        copy_req = inf_adopted_request_copy(request);

        inf_adopted_state_vector_add(
          inf_adopted_request_get_vector(copy_req),
          inf_user_get_id(INF_USER(user)),
          i
        );

        inf_adopted_algorithm_receive_request(priv->algorithm, copy_req);
        g_object_unref(copy_req);
      }
    }

    g_object_unref(request);

    /* Requests can always be forwarded since user is given. */
    return INF_COMMUNICATION_SCOPE_GROUP;
  }

  return INF_SESSION_CLASS(parent_class)->process_xml_run(
    session,
    connection,
    xml,
    error
  );
}
/* TODO: Move into request log class? */
static gboolean
inf_adopted_session_validate_request(InfAdoptedRequestLog* log,
                                     InfAdoptedRequest* request,
                                     GError** error)
{
  InfAdoptedStateVector* vector;
  guint user_id;
  guint n;

  guint begin;
  guint end;

  vector = inf_adopted_request_get_vector(request);
  user_id = inf_adopted_request_get_user_id(request);
  n = inf_adopted_state_vector_get(vector, user_id);
  
  begin = inf_adopted_request_log_get_begin(log);
  end = inf_adopted_request_log_get_end(log);

  /* TODO: Actually, begin != end is only relevant for the first request
   * in request log. */
  if(end != n && begin != end)
  {
    g_set_error(
      error,
      inf_adopted_session_error_quark,
      INF_ADOPTED_SESSION_ERROR_INVALID_REQUEST,
      _("Request has index '%u', but index '%u' was expected"),
      n,
      inf_adopted_request_log_get_end(log)
    );

    return FALSE;
  }
  else
  {
    switch(inf_adopted_request_get_request_type(request))
    {
    case INF_ADOPTED_REQUEST_DO:
      /* Nothing to check for */
      return TRUE;
    case INF_ADOPTED_REQUEST_UNDO:
      if(inf_adopted_request_log_next_undo(log) == NULL)
      {
        g_set_error(
          error,
          inf_adopted_session_error_quark,
          INF_ADOPTED_SESSION_ERROR_INVALID_REQUEST,
          "%s",
          _("Undo received, but no previous request found")
        );

        return FALSE;
      }
      else
      {
        return TRUE;
      }
    case INF_ADOPTED_REQUEST_REDO:
      if(inf_adopted_request_log_next_redo(log) == NULL)
      {
        g_set_error(
          error,
          inf_adopted_session_error_quark,
          INF_ADOPTED_SESSION_ERROR_INVALID_REQUEST,
          "%s",
          _("Redo received, but no previous request found")
        );

        return FALSE;
      }
      else
      {
        return TRUE;
      }
    default:
      g_assert_not_reached();
      return FALSE;
    }
  }
}
static gboolean
infinoted_plugin_transformation_protection_check_request_cb(InfAdoptedSession* session,
                                                            InfAdoptedRequest* request,
                                                            InfAdoptedUser* user,
                                                            gpointer user_data)
{
  InfinotedPluginTransformationProtectionSessionInfo* info;
  guint vdiff;
  InfXmlConnection* connection;
  gchar* request_str;
  gchar* current_str;
  gchar* remote_id;
  gchar* path;

  info = (InfinotedPluginTransformationProtectionSessionInfo*)user_data;

  vdiff = inf_adopted_state_vector_vdiff(
    inf_adopted_request_get_vector(request),
    inf_adopted_algorithm_get_current(
      inf_adopted_session_get_algorithm(session)
    )
  );

  if(vdiff > info->plugin->max_vdiff)
  {
    connection = inf_user_get_connection(INF_USER(user));

    /* Local requests do not need to be transformed, so always have a
     * zero vdiff. */
    g_assert(connection != NULL);

    /* Kill the connection */
    infd_session_proxy_unsubscribe(
      INFD_SESSION_PROXY(info->proxy),
      connection
    );

    /* Write a log message */
    path = inf_browser_get_path(
      INF_BROWSER(
        infinoted_plugin_manager_get_directory(info->plugin->manager)
      ),
      &info->iter
    );

    request_str = inf_adopted_state_vector_to_string(
      inf_adopted_request_get_vector(request)
    );

    current_str = inf_adopted_state_vector_to_string(
      inf_adopted_algorithm_get_current(
        inf_adopted_session_get_algorithm(session)
      )
    );

    g_object_get(G_OBJECT(connection), "remote-id", &remote_id, NULL);

    infinoted_log_warning(
      infinoted_plugin_manager_get_log(info->plugin->manager),
      _("In document \"%s\": Attempt to transform request \"%s\" to current state \"%s\" "
        "(vdiff=%u) by user \"%s\" (id=%u, conn=%s). Maximum allowed is %u; the "
        "connection has been unsubscribed."),
      path,
      request_str,
      current_str,
      vdiff,
      inf_user_get_name(INF_USER(user)),
      inf_user_get_id(INF_USER(user)),
      remote_id,
      info->plugin->max_vdiff
    );

    g_free(path);
    g_free(request_str);
    g_free(current_str);
    g_free(remote_id);

    /* Prevent the request from being transformed */
    return TRUE;
  }

  return FALSE;
}