// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "flutter/shell/platform/linux/public/flutter_linux/fl_method_call.h"
#include "flutter/shell/platform/linux/fl_method_call_private.h"
#include "flutter/shell/platform/linux/fl_method_channel_private.h"

#include <gmodule.h>

struct _FlMethodCall {
  GObject parent_instance;

  // Name of method being called.
  gchar* name;

  // Arguments provided to method call.
  FlValue* args;

  // Channel to respond on.
  FlMethodChannel* channel;
  FlBinaryMessengerResponseHandle* response_handle;
};

G_DEFINE_TYPE(FlMethodCall, fl_method_call, G_TYPE_OBJECT)

static void fl_method_call_dispose(GObject* object) {
  FlMethodCall* self = FL_METHOD_CALL(object);

  g_clear_pointer(&self->name, g_free);
  g_clear_pointer(&self->args, fl_value_unref);
  g_clear_object(&self->channel);
  g_clear_object(&self->response_handle);

  G_OBJECT_CLASS(fl_method_call_parent_class)->dispose(object);
}

static void fl_method_call_class_init(FlMethodCallClass* klass) {
  G_OBJECT_CLASS(klass)->dispose = fl_method_call_dispose;
}

static void fl_method_call_init(FlMethodCall* self) {}

FlMethodCall* fl_method_call_new(
    const gchar* name,
    FlValue* args,
    FlMethodChannel* channel,
    FlBinaryMessengerResponseHandle* response_handle) {
  g_return_val_if_fail(name != nullptr, nullptr);
  g_return_val_if_fail(args != nullptr, nullptr);
  g_return_val_if_fail(FL_IS_METHOD_CHANNEL(channel), nullptr);
  g_return_val_if_fail(FL_IS_BINARY_MESSENGER_RESPONSE_HANDLE(response_handle),
                       nullptr);

  FlMethodCall* self =
      FL_METHOD_CALL(g_object_new(fl_method_call_get_type(), nullptr));

  self->name = g_strdup(name);
  self->args = fl_value_ref(args);
  self->channel = FL_METHOD_CHANNEL(g_object_ref(channel));
  self->response_handle =
      FL_BINARY_MESSENGER_RESPONSE_HANDLE(g_object_ref(response_handle));

  return self;
}

G_MODULE_EXPORT const gchar* fl_method_call_get_name(FlMethodCall* self) {
  g_return_val_if_fail(FL_IS_METHOD_CALL(self), nullptr);
  return self->name;
}

G_MODULE_EXPORT FlValue* fl_method_call_get_args(FlMethodCall* self) {
  g_return_val_if_fail(FL_IS_METHOD_CALL(self), nullptr);
  return self->args;
}

G_MODULE_EXPORT gboolean fl_method_call_respond(FlMethodCall* self,
                                                FlMethodResponse* response,
                                                GError** error) {
  g_return_val_if_fail(FL_IS_METHOD_CALL(self), FALSE);
  g_return_val_if_fail(FL_IS_METHOD_RESPONSE(response), FALSE);

  g_autoptr(GError) local_error = nullptr;
  if (!fl_method_channel_respond(self->channel, self->response_handle, response,
                                 &local_error)) {
    // If the developer chose not to handle the error then log it so it's not
    // missed.
    if (error == nullptr) {
      g_warning("Failed to send method call response: %s",
                local_error->message);
    }

    g_propagate_error(error, local_error);
    return FALSE;
  }

  return TRUE;
}

G_MODULE_EXPORT gboolean fl_method_call_respond_success(FlMethodCall* self,
                                                        FlValue* result,
                                                        GError** error) {
  g_return_val_if_fail(FL_IS_METHOD_CALL(self), FALSE);

  g_autoptr(FlMethodResponse) response =
      FL_METHOD_RESPONSE(fl_method_success_response_new(result));
  return fl_method_channel_respond(self->channel, self->response_handle,
                                   response, error);
}

G_MODULE_EXPORT gboolean fl_method_call_respond_error(FlMethodCall* self,
                                                      const gchar* code,
                                                      const gchar* message,
                                                      FlValue* details,
                                                      GError** error) {
  g_return_val_if_fail(FL_IS_METHOD_CALL(self), FALSE);
  g_return_val_if_fail(code != nullptr, FALSE);

  g_autoptr(FlMethodResponse) response =
      FL_METHOD_RESPONSE(fl_method_error_response_new(code, message, details));
  return fl_method_channel_respond(self->channel, self->response_handle,
                                   response, error);
}

G_MODULE_EXPORT gboolean
fl_method_call_respond_not_implemented(FlMethodCall* self, GError** error) {
  g_return_val_if_fail(FL_IS_METHOD_CALL(self), FALSE);

  g_autoptr(FlMethodResponse) response =
      FL_METHOD_RESPONSE(fl_method_not_implemented_response_new());
  return fl_method_channel_respond(self->channel, self->response_handle,
                                   response, error);
}
