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

#include <stdarg.h>
#include <stdlib.h>

using namespace apache::thrift::concurrency;

namespace {

// Define environment variable DEBUG_EVENTLOG to enable debug logging
// ex: $ DEBUG_EVENTLOG=1 processor_test
static const char * DEBUG_EVENTLOG = getenv("DEBUG_EVENTLOG");

void debug(const char* fmt, ...) {
  if (DEBUG_EVENTLOG) {
    va_list ap;
    va_start(ap, fmt);
    vfprintf(stderr, fmt, ap);
    va_end(ap);

    fprintf(stderr, "\n");
  }
}
}

namespace apache {
namespace thrift {
namespace test {

uint32_t EventLog::nextId_ = 0;

#define EVENT_TYPE(value) EventType EventLog::value = #value
EVENT_TYPE(ET_LOG_END);
EVENT_TYPE(ET_CONN_CREATED);
EVENT_TYPE(ET_CONN_DESTROYED);
EVENT_TYPE(ET_CALL_STARTED);
EVENT_TYPE(ET_CALL_FINISHED);
EVENT_TYPE(ET_PROCESS);
EVENT_TYPE(ET_PRE_READ);
EVENT_TYPE(ET_POST_READ);
EVENT_TYPE(ET_PRE_WRITE);
EVENT_TYPE(ET_POST_WRITE);
EVENT_TYPE(ET_ASYNC_COMPLETE);
EVENT_TYPE(ET_HANDLER_ERROR);

EVENT_TYPE(ET_CALL_INCREMENT_GENERATION);
EVENT_TYPE(ET_CALL_GET_GENERATION);
EVENT_TYPE(ET_CALL_ADD_STRING);
EVENT_TYPE(ET_CALL_GET_STRINGS);
EVENT_TYPE(ET_CALL_GET_DATA_WAIT);
EVENT_TYPE(ET_CALL_ONEWAY_WAIT);
EVENT_TYPE(ET_CALL_EXCEPTION_WAIT);
EVENT_TYPE(ET_CALL_UNEXPECTED_EXCEPTION_WAIT);
EVENT_TYPE(ET_CALL_SET_VALUE);
EVENT_TYPE(ET_CALL_GET_VALUE);
EVENT_TYPE(ET_WAIT_RETURN);

EventLog::EventLog() {
  id_ = nextId_++;
  debug("New log: %d", id_);
}

void EventLog::append(EventType type,
                      uint32_t connectionId,
                      uint32_t callId,
                      const std::string& message) {
  Synchronized s(monitor_);
  debug("%d <-- %u, %u, %s \"%s\"", id_, connectionId, callId, type, message.c_str());

  Event e(type, connectionId, callId, message);
  events_.push_back(e);

  monitor_.notify();
}

Event EventLog::waitForEvent(int64_t timeout) {
  Synchronized s(monitor_);

  try {
    while (events_.empty()) {
      monitor_.wait(timeout);
    }
  } catch (const TimedOutException &) {
    return Event(ET_LOG_END, 0, 0, "");
  }

  Event event = events_.front();
  events_.pop_front();
  return event;
}

Event EventLog::waitForConnEvent(uint32_t connId, int64_t timeout) {
  Synchronized s(monitor_);

  auto it = events_.begin();
  while (true) {
    try {
      // TODO: it would be nicer to honor timeout for the duration of this
      // call, rather than restarting it for each call to wait().  It shouldn't
      // be a big problem in practice, though.
      while (it == events_.end()) {
        monitor_.wait(timeout);
      }
    } catch (const TimedOutException &) {
      return Event(ET_LOG_END, 0, 0, "");
    }

    if (it->connectionId == connId) {
      Event event = *it;
      events_.erase(it);
      return event;
    }
  }
}
}
}
} // apache::thrift::test
