// 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_message_codec.h"
#include "flutter/shell/platform/linux/fl_standard_message_codec_private.h"

#include <gmodule.h>

#include <cstring>

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

// Type values.
static constexpr int kValueNull = 0;
static constexpr int kValueTrue = 1;
static constexpr int kValueFalse = 2;
static constexpr int kValueInt32 = 3;
static constexpr int kValueInt64 = 4;
static constexpr int kValueFloat64 = 6;
static constexpr int kValueString = 7;
static constexpr int kValueUint8List = 8;
static constexpr int kValueInt32List = 9;
static constexpr int kValueInt64List = 10;
static constexpr int kValueFloat64List = 11;
static constexpr int kValueList = 12;
static constexpr int kValueMap = 13;
static constexpr int kValueFloat32List = 14;

struct _FlStandardMessageCodec {
  FlMessageCodec parent_instance;
};

G_DEFINE_TYPE(FlStandardMessageCodec,
              fl_standard_message_codec,
              fl_message_codec_get_type())

// Functions to write standard C number types.

static void write_uint8(GByteArray* buffer, uint8_t value) {
  g_byte_array_append(buffer, &value, sizeof(uint8_t));
}

static void write_uint16(GByteArray* buffer, uint16_t value) {
  g_byte_array_append(buffer, reinterpret_cast<uint8_t*>(&value),
                      sizeof(uint16_t));
}

static void write_uint32(GByteArray* buffer, uint32_t value) {
  g_byte_array_append(buffer, reinterpret_cast<uint8_t*>(&value),
                      sizeof(uint32_t));
}

static void write_int32(GByteArray* buffer, int32_t value) {
  g_byte_array_append(buffer, reinterpret_cast<uint8_t*>(&value),
                      sizeof(int32_t));
}

static void write_int64(GByteArray* buffer, int64_t value) {
  g_byte_array_append(buffer, reinterpret_cast<uint8_t*>(&value),
                      sizeof(int64_t));
}

static void write_float64(GByteArray* buffer, double value) {
  g_byte_array_append(buffer, reinterpret_cast<uint8_t*>(&value),
                      sizeof(double));
}

// Write padding bytes to align to @align multiple of bytes.
static void write_align(GByteArray* buffer, guint align) {
  while (buffer->len % align != 0) {
    write_uint8(buffer, 0);
  }
}

// Checks there is enough data in @buffer to be read.
static gboolean check_size(GBytes* buffer,
                           size_t offset,
                           size_t required,
                           GError** error) {
  if (offset + required > g_bytes_get_size(buffer)) {
    g_set_error(error, FL_MESSAGE_CODEC_ERROR,
                FL_MESSAGE_CODEC_ERROR_OUT_OF_DATA, "Unexpected end of data");
    return FALSE;
  }
  return TRUE;
}

// Skip bytes to align next read on @align multiple of bytes.
static gboolean read_align(GBytes* buffer,
                           size_t* offset,
                           size_t align,
                           GError** error) {
  if ((*offset) % align == 0) {
    return TRUE;
  }

  size_t required = align - (*offset) % align;
  if (!check_size(buffer, *offset, required, error)) {
    return FALSE;
  }

  (*offset) += required;
  return TRUE;
}

// Gets a pointer to the given offset in @buffer.
static const uint8_t* get_data(GBytes* buffer, size_t* offset) {
  return static_cast<const uint8_t*>(g_bytes_get_data(buffer, nullptr)) +
         *offset;
}

// Reads an unsigned 8 bit number from @buffer and writes it to @value.
// Returns TRUE if successful, otherwise sets an error.
static gboolean read_uint8(GBytes* buffer,
                           size_t* offset,
                           uint8_t* value,
                           GError** error) {
  if (!check_size(buffer, *offset, sizeof(uint8_t), error)) {
    return FALSE;
  }

  *value = get_data(buffer, offset)[0];
  (*offset)++;
  return TRUE;
}

// Reads an unsigned 16 bit integer from @buffer and writes it to @value.
// Returns TRUE if successful, otherwise sets an error.
static gboolean read_uint16(GBytes* buffer,
                            size_t* offset,
                            uint16_t* value,
                            GError** error) {
  if (!check_size(buffer, *offset, sizeof(uint16_t), error)) {
    return FALSE;
  }

  *value = reinterpret_cast<const uint16_t*>(get_data(buffer, offset))[0];
  *offset += sizeof(uint16_t);
  return TRUE;
}

// Reads an unsigned 32 bit integer from @buffer and writes it to @value.
// Returns TRUE if successful, otherwise sets an error.
static gboolean read_uint32(GBytes* buffer,
                            size_t* offset,
                            uint32_t* value,
                            GError** error) {
  if (!check_size(buffer, *offset, sizeof(uint32_t), error)) {
    return FALSE;
  }

  *value = reinterpret_cast<const uint32_t*>(get_data(buffer, offset))[0];
  *offset += sizeof(uint32_t);
  return TRUE;
}

// Reads a #FL_VALUE_TYPE_INT stored as a signed 32 bit integer from @buffer.
// Returns a new #FlValue of type #FL_VALUE_TYPE_INT if successful or %NULL on
// error.
static FlValue* read_int32_value(GBytes* buffer,
                                 size_t* offset,
                                 GError** error) {
  if (!check_size(buffer, *offset, sizeof(int32_t), error)) {
    return nullptr;
  }

  FlValue* value = fl_value_new_int(
      reinterpret_cast<const int32_t*>(get_data(buffer, offset))[0]);
  *offset += sizeof(int32_t);
  return value;
}

// Reads a #FL_VALUE_TYPE_INT stored as a signed 64 bit integer from @buffer.
// Returns a new #FlValue of type #FL_VALUE_TYPE_INT if successful or %NULL on
// error.
static FlValue* read_int64_value(GBytes* buffer,
                                 size_t* offset,
                                 GError** error) {
  if (!check_size(buffer, *offset, sizeof(int64_t), error)) {
    return nullptr;
  }

  FlValue* value = fl_value_new_int(
      reinterpret_cast<const int64_t*>(get_data(buffer, offset))[0]);
  *offset += sizeof(int64_t);
  return value;
}

// Reads a 64 bit floating point number from @buffer and writes it to @value.
// Returns a new #FlValue of type #FL_VALUE_TYPE_FLOAT if successful or %NULL on
// error.
static FlValue* read_float64_value(GBytes* buffer,
                                   size_t* offset,
                                   GError** error) {
  if (!read_align(buffer, offset, 8, error)) {
    return nullptr;
  }
  if (!check_size(buffer, *offset, sizeof(double), error)) {
    return nullptr;
  }

  FlValue* value = fl_value_new_float(
      reinterpret_cast<const double*>(get_data(buffer, offset))[0]);
  *offset += sizeof(double);
  return value;
}

// Reads an UTF-8 text string from @buffer in standard codec format.
// Returns a new #FlValue of type #FL_VALUE_TYPE_STRING if successful or %NULL
// on error.
static FlValue* read_string_value(FlStandardMessageCodec* self,
                                  GBytes* buffer,
                                  size_t* offset,
                                  GError** error) {
  uint32_t length;
  if (!fl_standard_message_codec_read_size(self, buffer, offset, &length,
                                           error)) {
    return nullptr;
  }
  if (!check_size(buffer, *offset, length, error)) {
    return nullptr;
  }
  FlValue* value = fl_value_new_string_sized(
      reinterpret_cast<const gchar*>(get_data(buffer, offset)), length);
  *offset += length;
  return value;
}

// Reads an unsigned 8 bit list from @buffer in standard codec format.
// Returns a new #FlValue of type #FL_VALUE_TYPE_UINT8_LIST if successful or
// %NULL on error.
static FlValue* read_uint8_list_value(FlStandardMessageCodec* self,
                                      GBytes* buffer,
                                      size_t* offset,
                                      GError** error) {
  uint32_t length;
  if (!fl_standard_message_codec_read_size(self, buffer, offset, &length,
                                           error)) {
    return nullptr;
  }
  if (!check_size(buffer, *offset, sizeof(uint8_t) * length, error)) {
    return nullptr;
  }
  FlValue* value = fl_value_new_uint8_list(get_data(buffer, offset), length);
  *offset += length;
  return value;
}

// Reads a signed 32 bit list from @buffer in standard codec format.
// Returns a new #FlValue of type #FL_VALUE_TYPE_INT32_LIST if successful or
// %NULL on error.
static FlValue* read_int32_list_value(FlStandardMessageCodec* self,
                                      GBytes* buffer,
                                      size_t* offset,
                                      GError** error) {
  uint32_t length;
  if (!fl_standard_message_codec_read_size(self, buffer, offset, &length,
                                           error)) {
    return nullptr;
  }
  if (!read_align(buffer, offset, 4, error)) {
    return nullptr;
  }
  if (!check_size(buffer, *offset, sizeof(int32_t) * length, error)) {
    return nullptr;
  }
  FlValue* value = fl_value_new_int32_list(
      reinterpret_cast<const int32_t*>(get_data(buffer, offset)), length);
  *offset += sizeof(int32_t) * length;
  return value;
}

// Reads a signed 64 bit list from @buffer in standard codec format.
// Returns a new #FlValue of type #FL_VALUE_TYPE_INT64_LIST if successful or
// %NULL on error.
static FlValue* read_int64_list_value(FlStandardMessageCodec* self,
                                      GBytes* buffer,
                                      size_t* offset,
                                      GError** error) {
  uint32_t length;
  if (!fl_standard_message_codec_read_size(self, buffer, offset, &length,
                                           error)) {
    return nullptr;
  }
  if (!read_align(buffer, offset, 8, error)) {
    return nullptr;
  }
  if (!check_size(buffer, *offset, sizeof(int64_t) * length, error)) {
    return nullptr;
  }
  FlValue* value = fl_value_new_int64_list(
      reinterpret_cast<const int64_t*>(get_data(buffer, offset)), length);
  *offset += sizeof(int64_t) * length;
  return value;
}

// Reads a 32 bit floating point number list from @buffer in standard codec
// format. Returns a new #FlValue of type #FL_VALUE_TYPE_FLOAT32_LIST if
// successful or %NULL on error.
static FlValue* read_float32_list_value(FlStandardMessageCodec* self,
                                        GBytes* buffer,
                                        size_t* offset,
                                        GError** error) {
  uint32_t length;
  if (!fl_standard_message_codec_read_size(self, buffer, offset, &length,
                                           error)) {
    return nullptr;
  }
  if (!read_align(buffer, offset, 4, error)) {
    return nullptr;
  }
  if (!check_size(buffer, *offset, sizeof(float) * length, error)) {
    return nullptr;
  }
  FlValue* value = fl_value_new_float32_list(
      reinterpret_cast<const float*>(get_data(buffer, offset)), length);
  *offset += sizeof(float) * length;
  return value;
}

// Reads a floating point number list from @buffer in standard codec format.
// Returns a new #FlValue of type #FL_VALUE_TYPE_FLOAT_LIST if successful or
// %NULL on error.
static FlValue* read_float64_list_value(FlStandardMessageCodec* self,
                                        GBytes* buffer,
                                        size_t* offset,
                                        GError** error) {
  uint32_t length;
  if (!fl_standard_message_codec_read_size(self, buffer, offset, &length,
                                           error)) {
    return nullptr;
  }
  if (!read_align(buffer, offset, 8, error)) {
    return nullptr;
  }
  if (!check_size(buffer, *offset, sizeof(double) * length, error)) {
    return nullptr;
  }
  FlValue* value = fl_value_new_float_list(
      reinterpret_cast<const double*>(get_data(buffer, offset)), length);
  *offset += sizeof(double) * length;
  return value;
}

// Reads a list from @buffer in standard codec format.
// Returns a new #FlValue of type #FL_VALUE_TYPE_LIST if successful or %NULL on
// error.
static FlValue* read_list_value(FlStandardMessageCodec* self,
                                GBytes* buffer,
                                size_t* offset,
                                GError** error) {
  uint32_t length;
  if (!fl_standard_message_codec_read_size(self, buffer, offset, &length,
                                           error)) {
    return nullptr;
  }

  g_autoptr(FlValue) list = fl_value_new_list();
  for (size_t i = 0; i < length; i++) {
    g_autoptr(FlValue) child =
        fl_standard_message_codec_read_value(self, buffer, offset, error);
    if (child == nullptr) {
      return nullptr;
    }
    fl_value_append(list, child);
  }

  return fl_value_ref(list);
}

// Reads a map from @buffer in standard codec format.
// Returns a new #FlValue of type #FL_VALUE_TYPE_MAP if successful or %NULL on
// error.
static FlValue* read_map_value(FlStandardMessageCodec* self,
                               GBytes* buffer,
                               size_t* offset,
                               GError** error) {
  uint32_t length;
  if (!fl_standard_message_codec_read_size(self, buffer, offset, &length,
                                           error)) {
    return nullptr;
  }

  g_autoptr(FlValue) map = fl_value_new_map();
  for (size_t i = 0; i < length; i++) {
    g_autoptr(FlValue) key =
        fl_standard_message_codec_read_value(self, buffer, offset, error);
    if (key == nullptr) {
      return nullptr;
    }
    g_autoptr(FlValue) value =
        fl_standard_message_codec_read_value(self, buffer, offset, error);
    if (value == nullptr) {
      return nullptr;
    }
    fl_value_set(map, key, value);
  }

  return fl_value_ref(map);
}

// Implements FlMessageCodec::encode_message.
static GBytes* fl_standard_message_codec_encode_message(FlMessageCodec* codec,
                                                        FlValue* message,
                                                        GError** error) {
  FlStandardMessageCodec* self =
      reinterpret_cast<FlStandardMessageCodec*>(codec);

  g_autoptr(GByteArray) buffer = g_byte_array_new();
  if (!fl_standard_message_codec_write_value(self, buffer, message, error)) {
    return nullptr;
  }
  return g_byte_array_free_to_bytes(
      static_cast<GByteArray*>(g_steal_pointer(&buffer)));
}

// Implements FlMessageCodec::decode_message.
static FlValue* fl_standard_message_codec_decode_message(FlMessageCodec* codec,
                                                         GBytes* message,
                                                         GError** error) {
  if (g_bytes_get_size(message) == 0) {
    return fl_value_new_null();
  }

  FlStandardMessageCodec* self =
      reinterpret_cast<FlStandardMessageCodec*>(codec);

  size_t offset = 0;
  g_autoptr(FlValue) value =
      fl_standard_message_codec_read_value(self, message, &offset, error);
  if (value == nullptr) {
    return nullptr;
  }

  if (offset != g_bytes_get_size(message)) {
    g_set_error(error, FL_MESSAGE_CODEC_ERROR,
                FL_MESSAGE_CODEC_ERROR_ADDITIONAL_DATA,
                "Unused %zi bytes after standard message",
                g_bytes_get_size(message) - offset);
    return nullptr;
  }

  return fl_value_ref(value);
}

static void fl_standard_message_codec_class_init(
    FlStandardMessageCodecClass* klass) {
  FL_MESSAGE_CODEC_CLASS(klass)->encode_message =
      fl_standard_message_codec_encode_message;
  FL_MESSAGE_CODEC_CLASS(klass)->decode_message =
      fl_standard_message_codec_decode_message;
}

static void fl_standard_message_codec_init(FlStandardMessageCodec* self) {}

G_MODULE_EXPORT FlStandardMessageCodec* fl_standard_message_codec_new() {
  return static_cast<FlStandardMessageCodec*>(
      g_object_new(fl_standard_message_codec_get_type(), nullptr));
}

void fl_standard_message_codec_write_size(FlStandardMessageCodec* codec,
                                          GByteArray* buffer,
                                          uint32_t size) {
  if (size < 254) {
    write_uint8(buffer, size);
  } else if (size <= 0xffff) {
    write_uint8(buffer, 254);
    write_uint16(buffer, size);
  } else {
    write_uint8(buffer, 255);
    write_uint32(buffer, size);
  }
}

gboolean fl_standard_message_codec_read_size(FlStandardMessageCodec* codec,
                                             GBytes* buffer,
                                             size_t* offset,
                                             uint32_t* value,
                                             GError** error) {
  uint8_t value8;
  if (!read_uint8(buffer, offset, &value8, error)) {
    return FALSE;
  }

  if (value8 == 255) {
    if (!read_uint32(buffer, offset, value, error)) {
      return FALSE;
    }
  } else if (value8 == 254) {
    uint16_t value16;
    if (!read_uint16(buffer, offset, &value16, error)) {
      return FALSE;
    }
    *value = value16;
  } else {
    *value = value8;
  }

  return TRUE;
}

gboolean fl_standard_message_codec_write_value(FlStandardMessageCodec* self,
                                               GByteArray* buffer,
                                               FlValue* value,
                                               GError** error) {
  if (value == nullptr) {
    write_uint8(buffer, kValueNull);
    return TRUE;
  }

  switch (fl_value_get_type(value)) {
    case FL_VALUE_TYPE_NULL:
      write_uint8(buffer, kValueNull);
      return TRUE;
    case FL_VALUE_TYPE_BOOL:
      if (fl_value_get_bool(value)) {
        write_uint8(buffer, kValueTrue);
      } else {
        write_uint8(buffer, kValueFalse);
      }
      return TRUE;
    case FL_VALUE_TYPE_INT: {
      int64_t v = fl_value_get_int(value);
      if (v >= INT32_MIN && v <= INT32_MAX) {
        write_uint8(buffer, kValueInt32);
        write_int32(buffer, v);
      } else {
        write_uint8(buffer, kValueInt64);
        write_int64(buffer, v);
      }
      return TRUE;
    }
    case FL_VALUE_TYPE_FLOAT:
      write_uint8(buffer, kValueFloat64);
      write_align(buffer, 8);
      write_float64(buffer, fl_value_get_float(value));
      return TRUE;
    case FL_VALUE_TYPE_STRING: {
      write_uint8(buffer, kValueString);
      const char* text = fl_value_get_string(value);
      size_t length = strlen(text);
      fl_standard_message_codec_write_size(self, buffer, length);
      g_byte_array_append(buffer, reinterpret_cast<const uint8_t*>(text),
                          length);
      return TRUE;
    }
    case FL_VALUE_TYPE_UINT8_LIST: {
      write_uint8(buffer, kValueUint8List);
      size_t length = fl_value_get_length(value);
      fl_standard_message_codec_write_size(self, buffer, length);
      g_byte_array_append(buffer, fl_value_get_uint8_list(value),
                          sizeof(uint8_t) * length);
      return TRUE;
    }
    case FL_VALUE_TYPE_INT32_LIST: {
      write_uint8(buffer, kValueInt32List);
      size_t length = fl_value_get_length(value);
      fl_standard_message_codec_write_size(self, buffer, length);
      write_align(buffer, 4);
      g_byte_array_append(
          buffer,
          reinterpret_cast<const uint8_t*>(fl_value_get_int32_list(value)),
          sizeof(int32_t) * length);
      return TRUE;
    }
    case FL_VALUE_TYPE_INT64_LIST: {
      write_uint8(buffer, kValueInt64List);
      size_t length = fl_value_get_length(value);
      fl_standard_message_codec_write_size(self, buffer, length);
      write_align(buffer, 8);
      g_byte_array_append(
          buffer,
          reinterpret_cast<const uint8_t*>(fl_value_get_int64_list(value)),
          sizeof(int64_t) * length);
      return TRUE;
    }
    case FL_VALUE_TYPE_FLOAT32_LIST: {
      write_uint8(buffer, kValueFloat32List);
      size_t length = fl_value_get_length(value);
      fl_standard_message_codec_write_size(self, buffer, length);
      write_align(buffer, 4);
      g_byte_array_append(
          buffer,
          reinterpret_cast<const uint8_t*>(fl_value_get_float32_list(value)),
          sizeof(float) * length);
      return TRUE;
    }
    case FL_VALUE_TYPE_FLOAT_LIST: {
      write_uint8(buffer, kValueFloat64List);
      size_t length = fl_value_get_length(value);
      fl_standard_message_codec_write_size(self, buffer, length);
      write_align(buffer, 8);
      g_byte_array_append(
          buffer,
          reinterpret_cast<const uint8_t*>(fl_value_get_float_list(value)),
          sizeof(double) * length);
      return TRUE;
    }
    case FL_VALUE_TYPE_LIST:
      write_uint8(buffer, kValueList);
      fl_standard_message_codec_write_size(self, buffer,
                                           fl_value_get_length(value));
      for (size_t i = 0; i < fl_value_get_length(value); i++) {
        if (!fl_standard_message_codec_write_value(
                self, buffer, fl_value_get_list_value(value, i), error)) {
          return FALSE;
        }
      }
      return TRUE;
    case FL_VALUE_TYPE_MAP:
      write_uint8(buffer, kValueMap);
      fl_standard_message_codec_write_size(self, buffer,
                                           fl_value_get_length(value));
      for (size_t i = 0; i < fl_value_get_length(value); i++) {
        if (!fl_standard_message_codec_write_value(
                self, buffer, fl_value_get_map_key(value, i), error) ||
            !fl_standard_message_codec_write_value(
                self, buffer, fl_value_get_map_value(value, i), error)) {
          return FALSE;
        }
      }
      return TRUE;
  }

  g_set_error(error, FL_MESSAGE_CODEC_ERROR,
              FL_MESSAGE_CODEC_ERROR_UNSUPPORTED_TYPE,
              "Unexpected FlValue type %d", fl_value_get_type(value));
  return FALSE;
}

FlValue* fl_standard_message_codec_read_value(FlStandardMessageCodec* self,
                                              GBytes* buffer,
                                              size_t* offset,
                                              GError** error) {
  uint8_t type;
  if (!read_uint8(buffer, offset, &type, error)) {
    return nullptr;
  }

  g_autoptr(FlValue) value = nullptr;
  if (type == kValueNull) {
    return fl_value_new_null();
  } else if (type == kValueTrue) {
    return fl_value_new_bool(TRUE);
  } else if (type == kValueFalse) {
    return fl_value_new_bool(FALSE);
  } else if (type == kValueInt32) {
    value = read_int32_value(buffer, offset, error);
  } else if (type == kValueInt64) {
    value = read_int64_value(buffer, offset, error);
  } else if (type == kValueFloat64) {
    value = read_float64_value(buffer, offset, error);
  } else if (type == kValueString) {
    value = read_string_value(self, buffer, offset, error);
  } else if (type == kValueUint8List) {
    value = read_uint8_list_value(self, buffer, offset, error);
  } else if (type == kValueInt32List) {
    value = read_int32_list_value(self, buffer, offset, error);
  } else if (type == kValueInt64List) {
    value = read_int64_list_value(self, buffer, offset, error);
  } else if (type == kValueFloat32List) {
    value = read_float32_list_value(self, buffer, offset, error);
  } else if (type == kValueFloat64List) {
    value = read_float64_list_value(self, buffer, offset, error);
  } else if (type == kValueList) {
    value = read_list_value(self, buffer, offset, error);
  } else if (type == kValueMap) {
    value = read_map_value(self, buffer, offset, error);
  } else {
    g_set_error(error, FL_MESSAGE_CODEC_ERROR,
                FL_MESSAGE_CODEC_ERROR_UNSUPPORTED_TYPE,
                "Unexpected standard codec type %02x", type);
    return nullptr;
  }

  return value == nullptr ? nullptr : fl_value_ref(value);
}
