/*
 * 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;

import com.scytl.slogger.crypto.SecureLoggerCryptographyFactory;
import com.scytl.slogger.crypto.SecureLoggerCryptographyProvider;
import com.scytl.slogger.policy.LogFileNamePolicyException;
import com.scytl.slogger.util.Securities;

import org.apache.log4j.Level;
import org.apache.log4j.Logger;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;

import junit.framework.TestCase;

/**
 *
 */
public abstract class SecureFileAppenderTester extends TestCase {

  private static final String LINE_SEPARATOR = System.getProperty("line.separator");

  private static final int LOG_TRACES = 10;

  private static final String TEST_APPENDER = "TEST_APPENDER";

  private static final String TEST_LOG = "test.log";

  private File _testLogFile;

  protected SecureFileAppender _secureFileAppender;

  private Logger _logger;

  private SecureLoggerCryptographyProvider _cryptographyProvider;

  /**
   * @return Returns the cryptographyProvider.
   */
  public SecureLoggerCryptographyProvider getCryptographyProvider() {
    return _cryptographyProvider;
  }

  /**
   * @throws Exception
   */
  public void testAppender() throws Exception {

    for (int i = 0; i < LOG_TRACES; i++) {
      _logger.info("log n." + i);
    }

    assertEquals(1, countLogFiles());
    verifyLog();
  }

  public void testDeprecatedSymbolsEscaping() throws Exception {
    StringBuilder sb;
    for (int i = 0; i < LOG_TRACES; i++) {
      sb = new StringBuilder(25);

      sb.append("log beginning ");
      sb.append(i);
      sb.append("\n\r\b\f|");
      sb.append(" log end");

      _logger.info(sb.toString());
    }

    assertEquals(1, countLogFiles());
    verifyLog();
    assertTrue(containsMessage("log beginning 0(n)(r)(b)(f)| log end"));
    assertTrue(containsMessage("log beginning 1(n)(r)(b)(f)| log end"));
    assertTrue(containsMessage("log beginning 2(n)(r)(b)(f)| log end"));
    assertTrue(containsMessage("log beginning 3(n)(r)(b)(f)| log end"));
  }

  public void testException() throws Exception {
    for (int i = 0; i < 3; i++) {
      _logger.error("Exception!", new Exception("log n. " + i));
    }

    assertEquals(1, countLogFiles());
    verifyLog();
  }

  public void testFile() throws Exception {
    StringBuilder content = new StringBuilder();
    try (InputStream stream = getClass().getResourceAsStream("/test.xml");
        Reader reader = new InputStreamReader(stream, StandardCharsets.UTF_8)) {
      int c;
      while ((c = reader.read()) != -1) {
        content.append((char)c);
      }
    }

    _logger.info(content.toString());
    _logger.info(content.toString());
    _logger.info("test trace");

    assertEquals(1, countLogFiles());
    verifyLog();
  }

  public void testLineSeparator() throws Exception {
    StringBuilder sb;
    for (int i = 0; i < LOG_TRACES; i++) {
      sb = new StringBuilder(25);

      sb.append("log n. ");
      sb.append(i);
      sb.append(LINE_SEPARATOR);
      sb.append("line 1");
      sb.append(LINE_SEPARATOR);
      sb.append("line 2");

      _logger.info(sb.toString());
    }

    assertEquals(1, countLogFiles());
    verifyLog();
  }

  /**
   * @throws Exception
   */
  public void testRollingAppender() throws Exception {
    _logger.removeAppender(_secureFileAppender);
    _secureFileAppender.close();
    CleanDirProvider.getCleanedTmpDir();
    _secureFileAppender = createSecureAppender();
    _secureFileAppender.setName(TEST_APPENDER);
    _secureFileAppender.setTriggeringTime(40);
    _secureFileAppender.activateOptions();
    _logger.addAppender(_secureFileAppender);

    for (int i = 0; i < LOG_TRACES; i++) {
      _logger.info("log n. " + i);
    }

    Thread.sleep(100);

    for (int i = 0; i < LOG_TRACES; i++) {
      _logger.info("log n. " + i);
    }

    Thread.sleep(100);

    for (int i = 0; i < LOG_TRACES; i++) {
      _logger.info("log n. " + i);
    }

    assertEquals(3, countLogFiles());
    verifyLog();
  }

  /**
   * @throws Exception
   */
  public void testSomeXMLAppenders() throws Exception {

    testXMLAppender();
    testXMLAppender();
    testXMLAppender();
  }

  /**
   * @throws Exception
   */
  public void testUnclosedLog() throws Exception {
    for (int i = 0; i < LOG_TRACES - 1; i++) {
      _logger.info("log n. " + i);
    }

    _secureFileAppender.forceClose();
    _logger.removeAppender(_secureFileAppender);
    initLogger();

    for (int i = 0; i < LOG_TRACES - 1; i++) {
      _logger.info("log n. " + i);
    }
    verifyLog();
  }

  /**
   * @throws Exception
   */
  public void testXMLAppender() throws Exception {

    for (int i = 0; i < LOG_TRACES; i++) {
      _logger.info("log n. " + i);
    }

    verifyLog();
  }

  protected abstract boolean containsMessage(String expectedMessage) throws IOException;

  /**
   * @throws Exception
   */
  protected abstract SecureFileAppender createSecureAppender() throws Exception;

  /**
   * @return Returns the testLogFile.
   */
  protected File getTestLogFile() {
    return _testLogFile;
  }

  @Override
  protected void setUp() throws Exception {
    Securities.addBouncyCastleProviderIfNecessary();
    _cryptographyProvider = new SecureLoggerCryptographyFactory().build();

    File tmpDir = CleanDirProvider.getCleanedTmpDir();
    _testLogFile = new File(tmpDir.getAbsolutePath(), TEST_LOG);
    initLogger();
  }

  @Override
  protected void tearDown() throws Exception {
    try {
      _logger.removeAppender(_secureFileAppender);
      _secureFileAppender.close();
      CleanDirProvider.getCleanedTmpDir();
    } finally {
      Securities.removeBouncyCastleProviderIfNecessary();
    }
  }

  protected abstract void verifyLog() throws Exception;

  private int countLogFiles() throws LogFileNamePolicyException {
    int count = 0;
    for (Iterator<File> it = _secureFileAppender.getLogFileNamePolicy().logFiles(); it.hasNext();) {
      it.next();
      count++;
    }

    return count;
  }

  private void initLogger() throws Exception {
    _secureFileAppender = createSecureAppender();
    _secureFileAppender.setName(TEST_APPENDER);
    _secureFileAppender.activateOptions();

    _logger = Logger.getLogger("testLog");
    _logger.addAppender(_secureFileAppender);
    _logger.setLevel(Level.INFO);
  }
}
