// 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_standard_method_codec.h"

#include <gmodule.h>

#include "flutter/shell/platform/linux/fl_standard_message_codec_private.h"
#include "flutter/shell/platform/linux/public/flutter_linux/fl_standard_message_codec.h"

// See lib/src/services/message_codecs.dart in Flutter source for description of
// encoding.

// Envelope codes.
static constexpr guint8 kEnvelopeTypeSuccess = 0;
static constexpr guint8 kEnvelopeTypeError = 1;

struct _FlStandardMethodCodec {
  FlMethodCodec parent_instance;

  FlStandardMessageCodec* codec;
};

G_DEFINE_TYPE(FlStandardMethodCodec,
              fl_standard_method_codec,
              fl_method_codec_get_type())

static void fl_standard_method_codec_dispose(GObject* object) {
  FlStandardMethodCodec* self = FL_STANDARD_METHOD_CODEC(object);

  g_clear_object(&self->codec);

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

// Implements FlMethodCodec::encode_method_call.
static GBytes* fl_standard_method_codec_encode_method_call(FlMethodCodec* codec,
                                                           const gchar* name,
                                                           FlValue* args,
                                                           GError** error) {
  FlStandardMethodCodec* self = FL_STANDARD_METHOD_CODEC(codec);

  g_autoptr(GByteArray) buffer = g_byte_array_new();
  g_autoptr(FlValue) name_value = fl_value_new_string(name);
  if (!fl_standard_message_codec_write_value(self->codec, buffer, name_value,
                                             error)) {
    return nullptr;
  }
  if (!fl_standard_message_codec_write_value(self->codec, buffer, args,
                                             error)) {
    return nullptr;
  }

  return g_byte_array_free_to_bytes(
      static_cast<GByteArray*>(g_steal_pointer(&buffer)));
}

// Implements FlMethodCodec::decode_method_call.
static gboolean fl_standard_method_codec_decode_method_call(
    FlMethodCodec* codec,
    GBytes* message,
    gchar** name,
    FlValue** args,
    GError** error) {
  FlStandardMethodCodec* self = FL_STANDARD_METHOD_CODEC(codec);

  size_t offset = 0;
  g_autoptr(FlValue) name_value = fl_standard_message_codec_read_value(
      self->codec, message, &offset, error);
  if (name_value == nullptr) {
    return FALSE;
  }
  if (fl_value_get_type(name_value) != FL_VALUE_TYPE_STRING) {
    g_set_error(error, FL_MESSAGE_CODEC_ERROR, FL_MESSAGE_CODEC_ERROR_FAILED,
                "Method call name wrong type");
    return FALSE;
  }

  g_autoptr(FlValue) args_value = fl_standard_message_codec_read_value(
      self->codec, message, &offset, error);
  if (args_value == nullptr) {
    return FALSE;
  }

  if (offset != g_bytes_get_size(message)) {
    g_set_error(error, FL_MESSAGE_CODEC_ERROR, FL_MESSAGE_CODEC_ERROR_FAILED,
                "Unexpected extra data");
    return FALSE;
  }

  *name = g_strdup(fl_value_get_string(name_value));
  *args = fl_value_ref(args_value);

  return TRUE;
}

// Implements FlMethodCodec::encode_success_envelope.
static GBytes* fl_standard_method_codec_encode_success_envelope(
    FlMethodCodec* codec,
    FlValue* result,
    GError** error) {
  FlStandardMethodCodec* self = FL_STANDARD_METHOD_CODEC(codec);

  g_autoptr(GByteArray) buffer = g_byte_array_new();
  guint8 type = kEnvelopeTypeSuccess;
  g_byte_array_append(buffer, &type, 1);
  if (!fl_standard_message_codec_write_value(self->codec, buffer, result,
                                             error)) {
    return nullptr;
  }

  return g_byte_array_free_to_bytes(
      static_cast<GByteArray*>(g_steal_pointer(&buffer)));
}

// Implements FlMethodCodec::encode_error_envelope.
static GBytes* fl_standard_method_codec_encode_error_envelope(
    FlMethodCodec* codec,
    const gchar* code,
    const gchar* message,
    FlValue* details,
    GError** error) {
  FlStandardMethodCodec* self = FL_STANDARD_METHOD_CODEC(codec);

  g_autoptr(GByteArray) buffer = g_byte_array_new();
  guint8 type = kEnvelopeTypeError;
  g_byte_array_append(buffer, &type, 1);
  g_autoptr(FlValue) code_value = fl_value_new_string(code);
  if (!fl_standard_message_codec_write_value(self->codec, buffer, code_value,
                                             error)) {
    return nullptr;
  }
  g_autoptr(FlValue) message_value =
      message != nullptr ? fl_value_new_string(message) : nullptr;
  if (!fl_standard_message_codec_write_value(self->codec, buffer, message_value,
                                             error)) {
    return nullptr;
  }
  if (!fl_standard_message_codec_write_value(self->codec, buffer, details,
                                             error)) {
    return nullptr;
  }

  return g_byte_array_free_to_bytes(
      static_cast<GByteArray*>(g_steal_pointer(&buffer)));
}

// Implements FlMethodCodec::encode_decode_response.
static FlMethodResponse* fl_standard_method_codec_decode_response(
    FlMethodCodec* codec,
    GBytes* message,
    GError** error) {
  FlStandardMethodCodec* self = FL_STANDARD_METHOD_CODEC(codec);

  if (g_bytes_get_size(message) == 0) {
    g_set_error(error, FL_MESSAGE_CODEC_ERROR,
                FL_MESSAGE_CODEC_ERROR_OUT_OF_DATA, "Empty response");
    return nullptr;
  }

  // First byte is response type.
  const guint8* data =
      static_cast<const guint8*>(g_bytes_get_data(message, nullptr));
  guint8 type = data[0];
  size_t offset = 1;

  g_autoptr(FlMethodResponse) response = nullptr;
  if (type == kEnvelopeTypeError) {
    g_autoptr(FlValue) code = fl_standard_message_codec_read_value(
        self->codec, message, &offset, error);
    if (code == nullptr) {
      return nullptr;
    }
    if (fl_value_get_type(code) != FL_VALUE_TYPE_STRING) {
      g_set_error(error, FL_MESSAGE_CODEC_ERROR, FL_MESSAGE_CODEC_ERROR_FAILED,
                  "Error code wrong type");
      return nullptr;
    }

    g_autoptr(FlValue) error_message = fl_standard_message_codec_read_value(
        self->codec, message, &offset, error);
    if (error_message == nullptr) {
      return nullptr;
    }
    if (fl_value_get_type(error_message) != FL_VALUE_TYPE_STRING &&
        fl_value_get_type(error_message) != FL_VALUE_TYPE_NULL) {
      g_set_error(error, FL_MESSAGE_CODEC_ERROR, FL_MESSAGE_CODEC_ERROR_FAILED,
                  "Error message wrong type");
      return nullptr;
    }

    g_autoptr(FlValue) details = fl_standard_message_codec_read_value(
        self->codec, message, &offset, error);
    if (details == nullptr) {
      return nullptr;
    }

    response = FL_METHOD_RESPONSE(fl_method_error_response_new(
        fl_value_get_string(code),
        fl_value_get_type(error_message) == FL_VALUE_TYPE_STRING
            ? fl_value_get_string(error_message)
            : nullptr,
        fl_value_get_type(details) != FL_VALUE_TYPE_NULL ? details : nullptr));
  } else if (type == kEnvelopeTypeSuccess) {
    g_autoptr(FlValue) result = fl_standard_message_codec_read_value(
        self->codec, message, &offset, error);

    if (result == nullptr) {
      return nullptr;
    }

    response = FL_METHOD_RESPONSE(fl_method_success_response_new(result));
  } else {
    g_set_error(error, FL_MESSAGE_CODEC_ERROR, FL_MESSAGE_CODEC_ERROR_FAILED,
                "Unknown envelope type %02x", type);
    return nullptr;
  }

  if (offset != g_bytes_get_size(message)) {
    g_set_error(error, FL_MESSAGE_CODEC_ERROR, FL_MESSAGE_CODEC_ERROR_FAILED,
                "Unexpected extra data");
    return nullptr;
  }

  return FL_METHOD_RESPONSE(g_object_ref(response));
}

static void fl_standard_method_codec_class_init(
    FlStandardMethodCodecClass* klass) {
  G_OBJECT_CLASS(klass)->dispose = fl_standard_method_codec_dispose;
  FL_METHOD_CODEC_CLASS(klass)->encode_method_call =
      fl_standard_method_codec_encode_method_call;
  FL_METHOD_CODEC_CLASS(klass)->decode_method_call =
      fl_standard_method_codec_decode_method_call;
  FL_METHOD_CODEC_CLASS(klass)->encode_success_envelope =
      fl_standard_method_codec_encode_success_envelope;
  FL_METHOD_CODEC_CLASS(klass)->encode_error_envelope =
      fl_standard_method_codec_encode_error_envelope;
  FL_METHOD_CODEC_CLASS(klass)->decode_response =
      fl_standard_method_codec_decode_response;
}

static void fl_standard_method_codec_init(FlStandardMethodCodec* self) {
  self->codec = fl_standard_message_codec_new();
}

G_MODULE_EXPORT FlStandardMethodCodec* fl_standard_method_codec_new() {
  return FL_STANDARD_METHOD_CODEC(
      g_object_new(fl_standard_method_codec_get_type(), nullptr));
}
