/**
 * Logback: the reliable, generic, fast and flexible logging framework.
 * Copyright (C) 1999-2015, QOS.ch. All rights reserved.
 *
 * This program and the accompanying materials are dual-licensed under
 * either the terms of the Eclipse Public License v1.0 as published by
 * the Eclipse Foundation
 *
 *   or (per the licensee's choosing)
 *
 * under the terms of the GNU Lesser General Public License version 2.1
 * as published by the Free Software Foundation.
 */
package ch.qos.logback.classic.net;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.LoggerFactory;

import ch.qos.logback.classic.ClassicTestConstants;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.joran.JoranConfigurator;
import ch.qos.logback.classic.net.mock.MockSyslogServer;
import ch.qos.logback.core.CoreConstants;
import ch.qos.logback.core.joran.spi.JoranException;
import ch.qos.logback.core.net.SyslogConstants;
import ch.qos.logback.core.recovery.RecoveryCoordinator;
import ch.qos.logback.core.testUtil.RandomUtil;
import ch.qos.logback.core.util.StatusPrinter;

import java.nio.charset.Charset;

public class SyslogAppenderTest {

  private static final String SYSLOG_PREFIX_REGEX = "<\\d{2}>\\w{3} [\\d ]\\d \\d{2}(:\\d{2}){2} [\\w.-]* ";

  LoggerContext lc = new LoggerContext();
  SyslogAppender sa = new SyslogAppender();
  MockSyslogServer mockServer;
  String loggerName = this.getClass().getName();
  Logger logger = lc.getLogger(loggerName);

  @Before
  public void setUp() throws Exception {
    lc.setName("test");
    sa.setContext(lc);
  }

  @After
  public void tearDown() throws Exception {
  }

  public void setMockServerAndConfigure(int expectedCount, String suffixPattern)
      throws InterruptedException {
    int port = RandomUtil.getRandomServerPort();

    mockServer = new MockSyslogServer(expectedCount, port);
    mockServer.start();
    // give MockSyslogServer head start

    Thread.sleep(100);

    sa.setSyslogHost("localhost");
    sa.setFacility("MAIL");
    sa.setPort(port);
    sa.setSuffixPattern(suffixPattern);
    sa.setStackTracePattern("[%thread] foo "+CoreConstants.TAB);
    sa.start();
    assertTrue(sa.isStarted());

    String loggerName = this.getClass().getName();
    Logger logger = lc.getLogger(loggerName);
    logger.addAppender(sa);

  }
  
  public void setMockServerAndConfigure(int expectedCount)
	      throws InterruptedException {
	 this.setMockServerAndConfigure(expectedCount, "[%thread] %logger %msg");   
  }

  @Test
  public void basic() throws InterruptedException {

    setMockServerAndConfigure(1);
    String logMsg = "hello";
    logger.debug(logMsg);

    // wait max 2 seconds for mock server to finish. However, it should
    // much sooner than that.
    mockServer.join(8000);

    assertTrue(mockServer.isFinished());
    assertEquals(1, mockServer.getMessageList().size());
    String msg = new String(mockServer.getMessageList().get(0));

    String threadName = Thread.currentThread().getName();

    String expected = "<"
        + (SyslogConstants.LOG_MAIL + SyslogConstants.DEBUG_SEVERITY) + ">";
    assertTrue(msg.startsWith(expected));

    checkRegexMatch(msg, SYSLOG_PREFIX_REGEX + "\\[" + threadName + "\\] " + loggerName + " "
        + logMsg);

  }
  
  @Test
  public void suffixPatternWithTag() throws InterruptedException {
    setMockServerAndConfigure(1, "test/something [%thread] %logger %msg");
    String logMsg = "hello";
    logger.debug(logMsg);

    // wait max 2 seconds for mock server to finish. However, it should
    // much sooner than that.
    mockServer.join(8000);

    assertTrue(mockServer.isFinished());
    assertEquals(1, mockServer.getMessageList().size());
    String msg = new String(mockServer.getMessageList().get(0));

    String threadName = Thread.currentThread().getName();

    String expected = "<"
        + (SyslogConstants.LOG_MAIL + SyslogConstants.DEBUG_SEVERITY) + ">";
    assertTrue(msg.startsWith(expected));

    checkRegexMatch(msg, SYSLOG_PREFIX_REGEX + "test/something \\[" + threadName + "\\] " + loggerName + " "
            + logMsg);

  }

  @Test
  public void tException() throws InterruptedException {
    setMockServerAndConfigure(21);

    String logMsg = "hello";
    String exMsg = "just testing";
    Exception ex = new Exception(exMsg);
    logger.debug(logMsg, ex);
    StatusPrinter.print(lc);

    // wait max 2 seconds for mock server to finish. However, it should
    // much sooner than that.
    mockServer.join(8000);
    assertTrue(mockServer.isFinished());

    // message + 20 lines of stacktrace
    assertEquals(21, mockServer.getMessageList().size());
    // int i = 0;
    // for (String line: mockServer.msgList) {
    // System.out.println(i++ + ": " + line);
    // }

    String msg = new String(mockServer.getMessageList().get(0));
    String expected = "<"
        + (SyslogConstants.LOG_MAIL + SyslogConstants.DEBUG_SEVERITY) + ">";
    assertTrue(msg.startsWith(expected));

    String threadName = Thread.currentThread().getName();
    String regex = SYSLOG_PREFIX_REGEX + "\\[" + threadName + "\\] " + loggerName
        + " " + logMsg;
    checkRegexMatch(msg, regex);

    msg = new String(mockServer.getMessageList().get(1));
    assertTrue(msg.contains(ex.getClass().getName()));
    assertTrue(msg.contains(ex.getMessage()));

    msg = new String(mockServer.getMessageList().get(2));
    assertTrue(msg.startsWith(expected));
    regex = SYSLOG_PREFIX_REGEX + "\\[" + threadName + "\\] " +  "foo "+CoreConstants.TAB + "at ch\\.qos.*";
    checkRegexMatch(msg, regex);
  }

  private void checkRegexMatch(String s, String regex) {
    assertTrue("The string [" + s + "] did not match regex [" + regex + "]", s
        .matches(regex));
  }

  @Test
  public void large() throws Exception {
    setMockServerAndConfigure(2);
    StringBuilder largeBuf = new StringBuilder();
    for (int i = 0; i < 2 * 1024 * 1024; i++) {
      largeBuf.append('a');
    }
    logger.debug(largeBuf.toString());

    String logMsg = "hello";
    logger.debug(logMsg);
    Thread.sleep(RecoveryCoordinator.BACKOFF_COEFFICIENT_MIN+10);
    logger.debug(logMsg);
    
    mockServer.join(8000);
    assertTrue(mockServer.isFinished());
 
    // both messages received
    assertEquals(2, mockServer.getMessageList().size());

    String expected = "<"
        + (SyslogConstants.LOG_MAIL + SyslogConstants.DEBUG_SEVERITY) + ">";
    String threadName = Thread.currentThread().getName();

    // large message is truncated
    final int maxMessageSize = sa.getMaxMessageSize();
    String largeMsg = new String(mockServer.getMessageList().get(0));
    assertTrue(largeMsg.startsWith(expected));
    String largeRegex = SYSLOG_PREFIX_REGEX + "\\[" + threadName + "\\] " + loggerName
        + " " + "a{" + (maxMessageSize - 2000) + "," + maxMessageSize + "}";
    checkRegexMatch(largeMsg, largeRegex);

    String msg = new String(mockServer.getMessageList().get(1));
    assertTrue(msg.startsWith(expected));
    String regex = SYSLOG_PREFIX_REGEX + "\\[" + threadName + "\\] " + loggerName
        + " " + logMsg;
    checkRegexMatch(msg, regex);
  }

  @Test
  public void LBCLASSIC_50() throws JoranException {

    LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();

    JoranConfigurator configurator = new JoranConfigurator();
    configurator.setContext(lc);
    lc.reset();
    configurator.doConfigure(ClassicTestConstants.JORAN_INPUT_PREFIX
        + "syslog_LBCLASSIC_50.xml");

    org.slf4j.Logger logger = LoggerFactory.getLogger(this.getClass());
    logger.info("hello");
  }

  @Test
  public void unknownHostShouldNotCauseStopToFail() {
    // See LOGBACK-960
    sa.setSyslogHost("unknown.host");
    sa.setFacility("MAIL");
    sa.start();
    sa.stop();
  }

  @Test
  public void nonAsciiMessageEncoding() throws Exception {
    // See LOGBACK-732
    setMockServerAndConfigure(1);

    // Use a string that can be encoded in a somewhat odd encoding (ISO-8859-4) to minimize
    // the probability of the encoding test to work by accident
    String logMsg = "Rīga";

    Charset ISO_8859_4 = Charset.forName("ISO-8859-4");
    sa.setCharset(ISO_8859_4);
    logger.debug(logMsg);

    // wait max 8 seconds for mock server to finish. However, it should
    // be done much sooner than that.
    mockServer.join(8000);

    assertTrue(mockServer.isFinished());
    assertEquals(1, mockServer.getMessageList().size());
    String msg = new String(mockServer.getMessageList().get(0), ISO_8859_4);
    String threadName = Thread.currentThread().getName();

    String expected = "<"
        + (SyslogConstants.LOG_MAIL + SyslogConstants.DEBUG_SEVERITY) + ">";
    assertTrue(msg.startsWith(expected));

    checkRegexMatch(msg, SYSLOG_PREFIX_REGEX + "\\[" + threadName + "\\] " + loggerName + " "
        + logMsg);

  }
}
