/*
 * 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.validator.hmac.encrypted;

import static java.util.Collections.unmodifiableList;

import com.scytl.slogger.event.SecureLoggingEvent;
import com.scytl.slogger.event.SecurePatternLayout;
import com.scytl.slogger.validator.EventValidatorException;

import java.io.BufferedWriter;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;

/**
 * Secure log block.
 */
public class SecureLogBlock {

  public static final String LINE_SEPARATOR = "\r";

  public static final String END_SECURE_BLOCK = "END SECURE BLOCK" + LINE_SEPARATOR;

  public static final String BEGIN_SECURE_BLOCK = "BEGIN SECURE BLOCK" + LINE_SEPARATOR;

  public static final String END_INSECURE_BLOCK = "END INSECURE BLOCK" + LINE_SEPARATOR;

  public static final String BEGIN_INSECURE_BLOCK = "BEGIN INSECURE BLOCK" + LINE_SEPARATOR;

  public static final String COMMENT = "# ";

  private final List<SecureLoggingEvent> originalLogList = new LinkedList<>();

  private final List<ProcessedLogEvent> okLogEventList = new LinkedList<>();

  private final List<ProcessedLogEvent> errorLogEventList = new LinkedList<>();

  /**
   * Adds a processing event.
   *
   * @param event the event.
   */
  public void addProcessedLogEvent(final SecureLoggingEvent event) {
    okLogEventList.add(new ProcessedLogEvent(event));
  }

  /**
   * Adds a processing event and an exception.
   *
   * @param event the event.
   */
  public void addProcessedLogEvent(final SecureLoggingEvent event,
      final EventValidatorException exception) {
    ProcessedLogEvent processedLogEvent = new ProcessedLogEvent(event, exception);
    okLogEventList.add(processedLogEvent);
    errorLogEventList.add(processedLogEvent);
  }

  /**
   * Loads a log event.
   *
   * @param event the event.
   */
  public void loadLogEvent(final SecureLoggingEvent event) {
    originalLogList.add(event);
  }

  /**
   * Returns the log events. The returned list is read-only.
   *
   * @return the log events.
   */
  public List<SecureLoggingEvent> logEvents() {
    return unmodifiableList(originalLogList);
  }

  /**
   * Saves the block when it is processed.
   *
   * @param okWriter the ok writer
   * @param errorWriter the error writer
   * @param warnWriter the wrning writer
   * @param layout the layout
   * @throws IOException I/O error occurred.
   */
  public void saveProcessedBlock(final BufferedWriter okWriter, final BufferedWriter errorWriter,
      final BufferedWriter warnWriter, final SecurePatternLayout layout) throws IOException {
    if (errorLogEventList.size() > 0) {
      saveErrorEventList(errorWriter, errorLogEventList, layout);

      if (okLogEventList.size() > 0) {
        writeComment(warnWriter, BEGIN_INSECURE_BLOCK);
        saveEventList(warnWriter, okLogEventList, layout);
        writeComment(warnWriter, END_INSECURE_BLOCK);
      }
    } else {
      if (okLogEventList.size() > 0) {
        writeComment(okWriter, BEGIN_SECURE_BLOCK);
        saveEventList(okWriter, okLogEventList, layout);
        writeComment(okWriter, END_SECURE_BLOCK);
      }
    }
  }

  private void saveErrorEventList(final BufferedWriter writer,
      final List<ProcessedLogEvent> logEventList, final SecurePatternLayout layout)
      throws IOException {
    for (ProcessedLogEvent secureLoggingEvent : logEventList) {
      writer.write(layout.format(secureLoggingEvent.getEvent()));
    }

    writer.flush();
  }

  private void saveEventList(final BufferedWriter writer,
      final List<ProcessedLogEvent> logEventList, final SecurePatternLayout layout)
      throws IOException {
    for (ProcessedLogEvent secureLoggingEvent : logEventList) {
      if (secureLoggingEvent.getException() != null) {
        writeComment(writer, secureLoggingEvent.getException().getMessage());
      } else {
        writer.write(layout.format(secureLoggingEvent.getEvent()));
      }
    }

    writer.flush();
  }

  private void writeComment(final BufferedWriter writer, final String comment) throws IOException {
    writer.write(COMMENT);
    writer.write(comment);
    writer.flush();
  }

  private static class ProcessedLogEvent {

    private final SecureLoggingEvent event;

    private final EventValidatorException exception;

    public ProcessedLogEvent(final SecureLoggingEvent event) {
      this(event, null);
    }

    public ProcessedLogEvent(final SecureLoggingEvent event,
        final EventValidatorException exception) {
      super();
      this.event = event;
      this.exception = exception;
    }

    public SecureLoggingEvent getEvent() {
      return event;
    }

    public EventValidatorException getException() {
      return exception;
    }
  }
}
