// 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 <signal.h>

#include "flutter/fml/build_config.h"
#include "flutter/fml/log_settings.h"
#include "flutter/fml/logging.h"
#include "gtest/gtest.h"

#if defined(OS_FUCHSIA)
#include <lib/syslog/global.h>
#include <lib/syslog/logger.h>
#include <lib/syslog/wire_format.h>
#include <lib/zx/socket.h>

#include "gmock/gmock.h"
#endif

namespace fml {
namespace testing {

#ifndef OS_FUCHSIA
class MakeSureFmlLogDoesNotSegfaultWhenStaticallyCalled {
 public:
  MakeSureFmlLogDoesNotSegfaultWhenStaticallyCalled() {
    SegfaultCatcher catcher;
    // If this line causes a segfault, FML is using a method of logging that is
    // not safe from static initialization on your platform.
    FML_LOG(INFO)
        << "This log exists to verify that static logging from FML works.";
  }

 private:
  struct SegfaultCatcher {
    typedef void (*sighandler_t)(int);

    SegfaultCatcher() {
      handler = ::signal(SIGSEGV, SegfaultHandler);
      FML_CHECK(handler != SIG_ERR);
    }

    ~SegfaultCatcher() { FML_CHECK(::signal(SIGSEGV, handler) != SIG_ERR); }

    static void SegfaultHandler(int signal) {
      fprintf(stderr,
              "FML failed to handle logging from static initialization.\n");
      exit(signal);
    }

    sighandler_t handler;
  };
};

static MakeSureFmlLogDoesNotSegfaultWhenStaticallyCalled fml_log_static_check_;
#endif  // !defined(OS_FUCHSIA)

int UnreachableScopeWithoutReturnDoesNotMakeCompilerMad() {
  KillProcess();
  // return 0; <--- Missing but compiler is fine.
}

int UnreachableScopeWithMacroWithoutReturnDoesNotMakeCompilerMad() {
  FML_UNREACHABLE();
  // return 0; <--- Missing but compiler is fine.
}

TEST(LoggingTest, UnreachableKillProcess) {
  ::testing::FLAGS_gtest_death_test_style = "threadsafe";
  ASSERT_DEATH(KillProcess(), "");
}

TEST(LoggingTest, UnreachableKillProcessWithMacro) {
  ::testing::FLAGS_gtest_death_test_style = "threadsafe";
  ASSERT_DEATH({ FML_UNREACHABLE(); }, "");
}

#if defined(OS_FUCHSIA)

struct LogPacket {
  fx_log_metadata_t metadata;
  std::vector<std::string> tags;
  std::string message;
};

class LoggingSocketTest : public ::testing::Test {
 protected:
  void SetUp() override {
    zx::socket local;
    ASSERT_EQ(ZX_OK, zx::socket::create(ZX_SOCKET_DATAGRAM, &local, &socket_));

    fx_logger_config_t config = {
        .min_severity = FX_LOG_INFO,
        .log_sink_socket = local.release(),
        .tags = nullptr,
        .num_tags = 0,
    };

    fx_log_reconfigure(&config);
  }

  LogPacket ReadPacket() {
    LogPacket result;
    fx_log_packet_t packet;
    zx_status_t res = socket_.read(0, &packet, sizeof(packet), nullptr);
    EXPECT_EQ(ZX_OK, res);
    result.metadata = packet.metadata;
    int pos = 0;
    while (packet.data[pos]) {
      int tag_len = packet.data[pos++];
      result.tags.emplace_back(packet.data + pos, tag_len);
      pos += tag_len;
    }
    result.message.append(packet.data + pos + 1);
    return result;
  }

  void ReadPacketAndCompare(fx_log_severity_t severity,
                            const std::string& message,
                            const std::vector<std::string>& tags = {}) {
    LogPacket packet = ReadPacket();
    EXPECT_EQ(severity, packet.metadata.severity);
    EXPECT_THAT(packet.message, ::testing::EndsWith(message));
    EXPECT_EQ(tags, packet.tags);
  }

  void CheckSocketEmpty() {
    zx_info_socket_t info = {};
    zx_status_t status =
        socket_.get_info(ZX_INFO_SOCKET, &info, sizeof(info), nullptr, nullptr);
    ASSERT_EQ(ZX_OK, status);
    EXPECT_EQ(0u, info.rx_buf_available);
  }

  zx::socket socket_;
};

TEST_F(LoggingSocketTest, UseSyslogOnFuchsia) {
  const char* msg1 = "test message";
  const char* msg2 = "hello";
  const char* msg3 = "logging";
  const char* msg4 = "Another message";
  const char* msg5 = "Foo";

  fml::SetLogSettings({.min_log_level = -1});

  FML_LOG(INFO) << msg1;
  ReadPacketAndCompare(FX_LOG_INFO, msg1);
  CheckSocketEmpty();

  FML_LOG(WARNING) << msg2;
  ReadPacketAndCompare(FX_LOG_WARNING, msg2);
  CheckSocketEmpty();

  FML_LOG(ERROR) << msg3;
  ReadPacketAndCompare(FX_LOG_ERROR, msg3);
  CheckSocketEmpty();

  FML_VLOG(1) << msg4;
  ReadPacketAndCompare(fx_log_severity_from_verbosity(1), msg4);
  CheckSocketEmpty();

  // VLOG(2) is not enabled so the log gets dropped.
  FML_VLOG(2) << msg5;
  CheckSocketEmpty();
}

#endif

}  // namespace testing
}  // namespace fml
