/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "bindings/c/hdfs.cc"
#include "common/logging.h"

#include <gmock/gmock.h>
#include <gtest/gtest.h>

#include <iostream>

using namespace hdfs;

struct log_state {
  int trace_count;
  int debug_count;
  int info_count;
  int warning_count;
  int error_count;

  int origin_unknown;
  int origin_rpc;
  int origin_blockreader;
  int origin_filehandle;
  int origin_filesystem;

  std::string msg;

  log_state() {
    reset();
  }

  void reset() {
    trace_count = 0;
    debug_count = 0;
    info_count = 0;
    warning_count = 0;
    error_count = 0;

    origin_unknown = 0;
    origin_rpc = 0;
    origin_blockreader = 0;
    origin_filehandle = 0;
    origin_filesystem = 0;

    msg = "";
  }
};
log_state log_state_instance;

void process_log_msg(LogData *data) {
  if(data->msg)
    log_state_instance.msg = data->msg;

  switch(data->level) {
    case HDFSPP_LOG_LEVEL_TRACE:
      log_state_instance.trace_count++;
      break;
    case HDFSPP_LOG_LEVEL_DEBUG:
      log_state_instance.debug_count++;
      break;
    case HDFSPP_LOG_LEVEL_INFO:
      log_state_instance.info_count++;
      break;
    case HDFSPP_LOG_LEVEL_WARN:
      log_state_instance.warning_count++;
      break;
    case HDFSPP_LOG_LEVEL_ERROR:
      log_state_instance.error_count++;
      break;
    default:
      //should never happen
      std::cout << "foo" << std::endl;
      ASSERT_FALSE(true);
  }

  switch(data->component) {
    case HDFSPP_LOG_COMPONENT_UNKNOWN:
      log_state_instance.origin_unknown++;
      break;
    case HDFSPP_LOG_COMPONENT_RPC:
      log_state_instance.origin_rpc++;
      break;
    case HDFSPP_LOG_COMPONENT_BLOCKREADER:
      log_state_instance.origin_blockreader++;
      break;
    case HDFSPP_LOG_COMPONENT_FILEHANDLE:
      log_state_instance.origin_filehandle++;
      break;
    case HDFSPP_LOG_COMPONENT_FILESYSTEM:
      log_state_instance.origin_filesystem++;
      break;
    default:
      std::cout << "bar" << std::endl;
      ASSERT_FALSE(true);
  }

}

void reset_log_counters() {
  log_state_instance.reset();
}

void assert_nothing_logged() {
  if(log_state_instance.trace_count || log_state_instance.debug_count ||
     log_state_instance.info_count || log_state_instance.warning_count ||
     log_state_instance.error_count) {
    ASSERT_FALSE(true);
  }
}

void assert_trace_logged() { ASSERT_TRUE(log_state_instance.trace_count > 0); }
void assert_debug_logged() { ASSERT_TRUE(log_state_instance.debug_count > 0); }
void assert_info_logged() { ASSERT_TRUE(log_state_instance.info_count > 0); }
void assert_warning_logged() { ASSERT_TRUE(log_state_instance.warning_count > 0); }
void assert_error_logged() { ASSERT_TRUE(log_state_instance.error_count > 0); }

void assert_no_trace_logged() { ASSERT_EQ(log_state_instance.trace_count, 0); }
void assert_no_debug_logged() { ASSERT_EQ(log_state_instance.debug_count, 0); }
void assert_no_info_logged() { ASSERT_EQ(log_state_instance.info_count, 0); }
void assert_no_warning_logged() { ASSERT_EQ(log_state_instance.warning_count, 0); }
void assert_no_error_logged() { ASSERT_EQ(log_state_instance.error_count, 0); }

void assert_unknown_logged() { ASSERT_TRUE(log_state_instance.origin_unknown > 0); }
void assert_rpc_logged() { ASSERT_TRUE(log_state_instance.origin_rpc > 0); }
void assert_blockreader_logged() { ASSERT_TRUE(log_state_instance.origin_blockreader > 0); }
void assert_filehandle_logged() { ASSERT_TRUE(log_state_instance.origin_filehandle > 0); }
void assert_filesystem_logged() { ASSERT_TRUE(log_state_instance.origin_filesystem > 0); }

void assert_no_unknown_logged() { ASSERT_EQ(log_state_instance.origin_unknown, 0); }
void assert_no_rpc_logged() { ASSERT_EQ(log_state_instance.origin_rpc, 0); }
void assert_no_blockreader_logged() { ASSERT_EQ(log_state_instance.origin_blockreader, 0); }
void assert_no_filehandle_logged() { ASSERT_EQ(log_state_instance.origin_filehandle, 0); }
void assert_no_filesystem_logged() { ASSERT_EQ(log_state_instance.origin_filesystem, 0); }

void log_all_components_at_level(LogLevel lvl) {
  if(lvl == kTrace) {
    LOG_TRACE(kUnknown, << 'a');
    LOG_TRACE(kRPC, << 'b');
    LOG_TRACE(kBlockReader, << 'c');
    LOG_TRACE(kFileHandle, << 'd');
    LOG_TRACE(kFileSystem, << 'e');
  } else if (lvl == kDebug) {
    LOG_DEBUG(kUnknown, << 'a');
    LOG_DEBUG(kRPC, << 'b');
    LOG_DEBUG(kBlockReader, << 'c');
    LOG_DEBUG(kFileHandle, << 'd');
    LOG_DEBUG(kFileSystem, << 'e');
  } else if (lvl == kInfo) {
    LOG_INFO(kUnknown, << 'a');
    LOG_INFO(kRPC, << 'b');
    LOG_INFO(kBlockReader, << 'c');
    LOG_INFO(kFileHandle, << 'd');
    LOG_INFO(kFileSystem, << 'e');
  } else if (lvl == kWarning) {
    LOG_WARN(kUnknown, << 'a');
    LOG_WARN(kRPC, << 'b');
    LOG_WARN(kBlockReader, << 'c');
    LOG_WARN(kFileHandle, << 'd');
    LOG_WARN(kFileSystem, << 'e');
  } else if (lvl == kError) {
    LOG_ERROR(kUnknown, << 'a');
    LOG_ERROR(kRPC, << 'b');
    LOG_ERROR(kBlockReader, << 'c');
    LOG_ERROR(kFileHandle, << 'd');
    LOG_ERROR(kFileSystem, << 'e');
  } else {
    // A level was added and not accounted for here
    ASSERT_TRUE(false);
  }
}

// make sure everything can be masked
TEST(LoggingTest, MaskAll) {
  LogManager::DisableLogForComponent(kUnknown);
  LogManager::DisableLogForComponent(kRPC);
  LogManager::DisableLogForComponent(kBlockReader);
  LogManager::DisableLogForComponent(kFileHandle);
  LogManager::DisableLogForComponent(kFileSystem);

  // use trace so anything that isn't masked should come through
  LogManager::SetLogLevel(kTrace);
  log_state_instance.reset();
  log_all_components_at_level(kError);
  assert_nothing_logged();
  log_state_instance.reset();
}

// make sure components can be masked individually
TEST(LoggingTest, MaskOne) {
  LogManager::DisableLogForComponent(kUnknown);
  LogManager::DisableLogForComponent(kRPC);
  LogManager::DisableLogForComponent(kBlockReader);
  LogManager::DisableLogForComponent(kFileHandle);
  LogManager::DisableLogForComponent(kFileSystem);
  LogManager::SetLogLevel(kTrace);

  // Unknown - aka component not provided
  LogManager::EnableLogForComponent(kUnknown);
  log_all_components_at_level(kError);
  assert_unknown_logged();
  assert_error_logged();
  assert_no_rpc_logged();
  assert_no_blockreader_logged();
  assert_no_filehandle_logged();
  assert_no_filesystem_logged();
  log_state_instance.reset();
  LogManager::DisableLogForComponent(kUnknown);

  // RPC
  LogManager::EnableLogForComponent(kRPC);
  log_all_components_at_level(kError);
  assert_rpc_logged();
  assert_error_logged();
  assert_no_unknown_logged();
  assert_no_blockreader_logged();
  assert_no_filehandle_logged();
  assert_no_filesystem_logged();
  log_state_instance.reset();
  LogManager::DisableLogForComponent(kRPC);

  // BlockReader
  LogManager::EnableLogForComponent(kBlockReader);
  log_all_components_at_level(kError);
  assert_blockreader_logged();
  assert_error_logged();
  assert_no_unknown_logged();
  assert_no_rpc_logged();
  assert_no_filehandle_logged();
  assert_no_filesystem_logged();
  log_state_instance.reset();
  LogManager::DisableLogForComponent(kBlockReader);

  // FileHandle
  LogManager::EnableLogForComponent(kFileHandle);
  log_all_components_at_level(kError);
  assert_filehandle_logged();
  assert_error_logged();
  assert_no_unknown_logged();
  assert_no_rpc_logged();
  assert_no_blockreader_logged();
  assert_no_filesystem_logged();
  log_state_instance.reset();
  LogManager::DisableLogForComponent(kFileHandle);

  // FileSystem
  LogManager::EnableLogForComponent(kFileSystem);
  log_all_components_at_level(kError);
  assert_filesystem_logged();
  assert_error_logged();
  assert_no_unknown_logged();
  assert_no_rpc_logged();
  assert_no_blockreader_logged();
  assert_no_filehandle_logged();
  log_state_instance.reset();
  LogManager::DisableLogForComponent(kFileSystem);
}

TEST(LoggingTest, Levels) {
  // should be safe to focus on one component if MaskOne passes
  LogManager::EnableLogForComponent(kUnknown);
  LogManager::SetLogLevel(kError);

  LOG_TRACE(kUnknown, << "a");
  LOG_DEBUG(kUnknown, << "b");
  LOG_INFO(kUnknown,<< "c");
  LOG_WARN(kUnknown, << "d");
  assert_nothing_logged();
  LOG_ERROR(kUnknown, << "e");
  assert_error_logged();
  assert_unknown_logged();
  log_state_instance.reset();

  // anything >= warning
  LogManager::SetLogLevel(kWarning);
  LOG_TRACE(kUnknown, << "a");
  LOG_DEBUG(kUnknown, << "b");
  LOG_INFO(kUnknown, << "c");
  assert_nothing_logged();
  LOG_WARN(kUnknown, << "d");
  assert_warning_logged();
  LOG_ERROR(kUnknown, << "e");
  assert_error_logged();
  log_state_instance.reset();

  // anything >= info
  LogManager::SetLogLevel(kInfo);
  LOG_TRACE(kUnknown, << "a");
  LOG_DEBUG(kUnknown, << "b");
  assert_nothing_logged();
  LOG_INFO(kUnknown, << "c");
  assert_info_logged();
  LOG_WARN(kUnknown, << "d");
  assert_warning_logged();
  LOG_ERROR(kUnknown, << "e");
  assert_error_logged();
  log_state_instance.reset();

  // anything >= debug
  LogManager::SetLogLevel(kDebug);
  LOG_TRACE(kUnknown, << "a");
  assert_nothing_logged();
  LOG_DEBUG(kUnknown, << "b");
  assert_debug_logged();
  assert_no_info_logged();
  assert_no_warning_logged();
  assert_no_error_logged();
  LOG_INFO(kUnknown, << "c");
  assert_info_logged();
  assert_no_warning_logged();
  assert_no_error_logged();
  LOG_WARN(kUnknown, << "d");
  assert_warning_logged();
  assert_no_error_logged();
  LOG_ERROR(kUnknown, << "e");
  assert_error_logged();
  log_state_instance.reset();

  // anything
  LogManager::SetLogLevel(kTrace);
  assert_nothing_logged();
  LOG_TRACE(kUnknown, << "a");
  assert_trace_logged();
  log_state_instance.reset();
  LOG_DEBUG(kUnknown, << "b");
  assert_debug_logged();
  log_state_instance.reset();
  LOG_INFO(kUnknown, << "c");
  assert_info_logged();
  log_state_instance.reset();
  LOG_WARN(kUnknown, << "d");
  assert_warning_logged();
  log_state_instance.reset();
  LOG_ERROR(kUnknown, << "e");
  assert_error_logged();
}

TEST(LoggingTest, Text) {
  LogManager::EnableLogForComponent(kRPC);

  std::string text;
  LOG_ERROR(kRPC, << text);

  ASSERT_EQ(text, log_state_instance.msg);
}


int main(int argc, char *argv[]) {
  CForwardingLogger *logger = new CForwardingLogger();
  logger->SetCallback(process_log_msg);
  LogManager::SetLoggerImplementation(std::unique_ptr<LoggerInterface>(logger));

  // The following line must be executed to initialize Google Mock
  // (and Google Test) before running the tests.
  ::testing::InitGoogleMock(&argc, argv);
  int res = RUN_ALL_TESTS();
  google::protobuf::ShutdownProtobufLibrary();
  return res;
}
