package com.codecademy.eventhub.index;

import com.google.common.base.Function;
import com.google.common.collect.Lists;
import com.codecademy.eventhub.base.DB;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;

import java.io.Closeable;
import java.io.IOException;
import java.util.Collections;
import java.util.List;

/**
 * DatedEventIndex is responsible for tracking the earliest event id for a given date.
 */
public class DatedEventIndex implements Closeable {
  private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormat.forPattern("yyyyMMdd");
  private static final String DATE_PREFIX = "d";
  private static final String ID_PREFIX = "i";

  private final DB db;
  // O(numDays)
  private final List<String> dates;
  // O(numDays)
  private final List<Long> earliestEventIds;
  private String currentDate;

  public DatedEventIndex(DB db, List<String> dates, List<Long> earliestEventIds,
      String currentDate) {
    this.db = db;
    this.dates = dates;
    this.earliestEventIds = earliestEventIds;
    this.currentDate = currentDate;
  }

  public long findFirstEventIdOnDate(long eventIdForStartDate, int numDaysAfter) {
    int startDateOffset = Collections.binarySearch(earliestEventIds, eventIdForStartDate);
    if (startDateOffset < 0) {
      if (startDateOffset == -1) {
        startDateOffset = 0;
      } else {
        startDateOffset = -startDateOffset - 2;
      }
    }
    String dateOfEvent = dates.get(startDateOffset);
    String endDate = DATE_TIME_FORMATTER.print(
        DateTime.parse(dateOfEvent, DATE_TIME_FORMATTER).plusDays(numDaysAfter));
    int endDateOffset = Collections.binarySearch(dates, endDate);
    if (endDateOffset < 0) {
      endDateOffset = -endDateOffset - 1;
      if (endDateOffset >= earliestEventIds.size()) {
        return Long.MAX_VALUE;
      }
    }
    return earliestEventIds.get(endDateOffset);
  }

  public synchronized void addEvent(long eventId, String date) {
    if (currentDate != null && date.compareTo(currentDate) <= 0) {
      return;
    }
    currentDate = date;
    dates.add(date);
    earliestEventIds.add(eventId);

    db.put(DATE_PREFIX + date, "");
    db.put(ID_PREFIX + String.format("%020d", eventId), "");
  }

  public String getCurrentDate() {
    return currentDate;
  }

  @Override
  public void close() throws IOException {
    db.close();
  }

  public static DatedEventIndex create(DB db) {
    List<String> dates = db.findByPrefix(DATE_PREFIX, DATE_PREFIX.length());
    List<Long> earliestEventIds = Lists.newArrayList(
        Lists.transform(db.findByPrefix(ID_PREFIX, ID_PREFIX.length()), new Function<String, Long>() {
          @Override
          public Long apply(String s) {
            return Long.parseLong(s);
          }
        }));
    return new DatedEventIndex(db, dates, earliestEventIds,
        dates.isEmpty() ? "" : dates.get(dates.size() - 1));
  }
}
