/*
 * Copyright 2018 Scytl Secure Electronic Voting SA
 *
 * All rights reserved
 *
 * See our extended copyright notice in *file 'Copyright.txt'
 * which is part of this source code package
 */

package com.scytl.slogger.policy;

import static java.text.MessageFormat.format;
import static java.util.Arrays.asList;
import static java.util.Arrays.sort;

import java.io.File;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Iterator;
import java.util.List;

/**
 * Sequential date file name policy.
 */
public class SequentialDateFileNamePolicy extends LogFileNamePolicy {

  private static final String DOT = ".";

  private static final char SEPARATOR = '-';

  private final DateFormat format = new SimpleDateFormat("yyyyMMdd-HHmmss");

  private final String namePrefix;

  private final String nameSuffix;

  private final File folder;

  /**
   * Constructor.
   *
   * @param file the file.
   */
  public SequentialDateFileNamePolicy(final File file) {
    super(file);
    folder = getBaseFileName().getParentFile();
    String name = getBaseFileName().getName();
    int index = name.lastIndexOf(DOT);
    if (index >= 0) {
      namePrefix = name.substring(0, index) + SEPARATOR;
      nameSuffix = name.substring(index);
    } else {
      namePrefix = name + SEPARATOR;
      nameSuffix = "";
    }
  }

  @Override
  public File currentLogFile() throws LogFileNamePolicyException {
    return lastLogFile();
  }

  @Override
  public File lastLogFile() throws LogFileNamePolicyException {
    List<File> files = listLogFiles();
    return files.isEmpty() ? null : files.get(files.size() - 1);
  }

  @Override
  public Iterator<File> logFiles() throws LogFileNamePolicyException {
    return listLogFiles().iterator();
  }

  @Override
  public File nextLogFile() throws LogFileNamePolicyException {
    int index = 0;
    Date now = new Date();
    File lastFile = lastLogFile();
    if (lastFile != null) {
      IndexAndDate indexAndDate = extractIndexAndDate(lastFile);
      if (indexAndDate.date.after(now)) {
        throw new LogFileNamePolicyException(
            "Last log file date: " + indexAndDate.date + " is greater than current date: " + now);
      }
      index = indexAndDate.index + 1;
    }
    String date;
    synchronized (format) {
      date = format.format(now);
    }
    return new File(folder, namePrefix + date + SEPARATOR + index + nameSuffix);
  }

  private boolean acceptLogFiles(File pathname) {
    String name = pathname.getName();
    if (!name.startsWith(namePrefix) || !name.endsWith(nameSuffix)
        || name.length() <= namePrefix.length() + nameSuffix.length()) {
      return false;
    }
    String dateAndIndex = name.substring(namePrefix.length(), name.length() - nameSuffix.length());
    int indexOfSeparator = dateAndIndex.lastIndexOf(SEPARATOR);
    if (indexOfSeparator < 0) {
      return false;
    }
    try {
      synchronized (format) {
        format.parse(dateAndIndex.substring(0, indexOfSeparator));
      }
    } catch (ParseException e) {
      return false;
    }
    try {
      Integer.parseInt(dateAndIndex.substring(indexOfSeparator + 1));
    } catch (NumberFormatException e) {
      return false;
    }
    return true;
  }

  private int compareLogFiles(File f1, File f2) {
    return extractIndexAndDate(f1).index - extractIndexAndDate(f2).index;
  }

  private IndexAndDate extractIndexAndDate(final File file) {
    String name = file.getName();
    if (!name.startsWith(namePrefix) || !name.endsWith(nameSuffix)
        || name.length() <= namePrefix.length() + nameSuffix.length()) {
      throw new IllegalArgumentException(format("Invalid log file ''{0}''", file));
    }
    String source = name.substring(namePrefix.length(), name.length() - nameSuffix.length());
    int indexOfSeparator = source.lastIndexOf(SEPARATOR);
    if (indexOfSeparator < 0) {
      throw new IllegalArgumentException(format("Invalid log file ''{0}''", file));
    }
    Date date;
    try {
      synchronized (format) {
        date = format.parse(source.substring(0, indexOfSeparator));
      }
    } catch (ParseException e) {
      throw new IllegalArgumentException(format("Invalid log file ''{0}''", file), e);
    }
    int index;
    try {
      index = Integer.parseInt(source.substring(indexOfSeparator + 1));
    } catch (NumberFormatException e) {
      throw new IllegalArgumentException(format("Invalid log file ''{0}''", file), e);
    }
    return new IndexAndDate(index, date);
  }

  private List<File> listLogFiles() throws LogFileNamePolicyException {
    File[] files = folder.listFiles(this::acceptLogFiles);
    if (files == null) {
      throw new LogFileNamePolicyException(
          format("Log files folder ''{0}'' does not exist or is invalid.", folder));
    }
    sort(files, this::compareLogFiles);
    verifyFileOrder(files);
    return asList(files);
  }

  private void verifyFileOrder(final File[] files) throws LogFileNamePolicyException {
    Date lastDate = null;
    for (int i = 0; i < files.length; i++) {
      File file = files[i];
      IndexAndDate indexAndDate = extractIndexAndDate(file);
      if (indexAndDate.index != i) {
        throw new LogFileNamePolicyException(
            "File index order has been broken. File: " + file + " . Expected Index: " + i);
      }
      if (lastDate != null && indexAndDate.date.before(lastDate)) {
        throw new LogFileNamePolicyException(
            "File date " + lastDate + " is greater than " + indexAndDate.date);
      }
      lastDate = indexAndDate.date;
    }
  }

  private static class IndexAndDate {
    private final int index;

    private final Date date;

    public IndexAndDate(final int index, final Date date) {
      this.date = date;
      this.index = index;
    }
  }
}
