/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.hadoop.log;

import junit.framework.TestCase;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.util.Time;
import org.apache.log4j.Appender;
import org.apache.log4j.Category;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.WriterAppender;
import org.apache.log4j.spi.HierarchyEventListener;
import org.apache.log4j.spi.LoggerFactory;
import org.apache.log4j.spi.LoggerRepository;
import org.apache.log4j.spi.ThrowableInformation;
import org.codehaus.jackson.JsonFactory;
import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.map.MappingJsonFactory;
import org.codehaus.jackson.node.ContainerNode;
import org.junit.Test;

import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.net.NoRouteToHostException;
import java.util.Enumeration;
import java.util.Vector;

public class TestLog4Json extends TestCase {

  private static final Log LOG = LogFactory.getLog(TestLog4Json.class);
  private static final JsonFactory factory = new MappingJsonFactory();

  @Test
  public void testConstruction() throws Throwable {
    Log4Json l4j = new Log4Json();
    String outcome = l4j.toJson(new StringWriter(),
        "name", 0, "DEBUG", "thread1",
        "hello, world", null).toString();
    println("testConstruction", outcome);
  }

  @Test
  public void testException() throws Throwable {
    Exception e =
        new NoRouteToHostException("that box caught fire 3 years ago");
    ThrowableInformation ti = new ThrowableInformation(e);
    Log4Json l4j = new Log4Json();
    long timeStamp = Time.now();
    String outcome = l4j.toJson(new StringWriter(),
        "testException",
        timeStamp,
        "INFO",
        "quoted\"",
        "new line\n and {}",
        ti)
        .toString();
    println("testException", outcome);
  }

  @Test
  public void testNestedException() throws Throwable {
    Exception e =
        new NoRouteToHostException("that box caught fire 3 years ago");
    Exception ioe = new IOException("Datacenter problems", e);
    ThrowableInformation ti = new ThrowableInformation(ioe);
    Log4Json l4j = new Log4Json();
    long timeStamp = Time.now();
    String outcome = l4j.toJson(new StringWriter(),
        "testNestedException",
        timeStamp,
        "INFO",
        "quoted\"",
        "new line\n and {}",
        ti)
        .toString();
    println("testNestedException", outcome);
    ContainerNode rootNode = Log4Json.parse(outcome);
    assertEntryEquals(rootNode, Log4Json.LEVEL, "INFO");
    assertEntryEquals(rootNode, Log4Json.NAME, "testNestedException");
    assertEntryEquals(rootNode, Log4Json.TIME, timeStamp);
    assertEntryEquals(rootNode, Log4Json.EXCEPTION_CLASS,
        ioe.getClass().getName());
    JsonNode node = assertNodeContains(rootNode, Log4Json.STACK);
    assertTrue("Not an array: " + node, node.isArray());
    node = assertNodeContains(rootNode, Log4Json.DATE);
    assertTrue("Not a string: " + node, node.isTextual());
    //rather than try and make assertions about the format of the text
    //message equalling another ISO date, this test asserts that the hypen
    //and colon characters are in the string.
    String dateText = node.getTextValue();
    assertTrue("No '-' in " + dateText, dateText.contains("-"));
    assertTrue("No '-' in " + dateText, dateText.contains(":"));

  }


  /**
   * Create a log instance and and log to it
   * @throws Throwable if it all goes wrong
   */
  @Test
  public void testLog() throws Throwable {
    String message = "test message";
    Throwable throwable = null;
    String json = logOut(message, throwable);
    println("testLog", json);
  }

  /**
   * Create a log instance and and log to it
   * @throws Throwable if it all goes wrong
   */
  @Test
  public void testLogExceptions() throws Throwable {
    String message = "test message";
    Throwable inner = new IOException("Directory / not found");
    Throwable throwable = new IOException("startup failure", inner);
    String json = logOut(message, throwable);
    println("testLogExceptions", json);
  }


  void assertEntryEquals(ContainerNode rootNode, String key, String value) {
    JsonNode node = assertNodeContains(rootNode, key);
    assertEquals(value, node.getTextValue());
  }

  private JsonNode assertNodeContains(ContainerNode rootNode, String key) {
    JsonNode node = rootNode.get(key);
    if (node == null) {
      fail("No entry of name \"" + key + "\" found in " + rootNode.toString());
    }
    return node;
  }

  void assertEntryEquals(ContainerNode rootNode, String key, long value) {
    JsonNode node = assertNodeContains(rootNode, key);
    assertEquals(value, node.getNumberValue());
  }

  /**
   * Print out what's going on. The logging APIs aren't used and the text
   * delimited for more details
   *
   * @param name name of operation
   * @param text text to print
   */
  private void println(String name, String text) {
    System.out.println(name + ": #" + text + "#");
  }

  private String logOut(String message, Throwable throwable) {
    StringWriter writer = new StringWriter();
    Logger logger = createLogger(writer);
    logger.info(message, throwable);
    //remove and close the appender
    logger.removeAllAppenders();
    return writer.toString();
  }

  public Logger createLogger(Writer writer) {
    TestLoggerRepository repo = new TestLoggerRepository();
    Logger logger = repo.getLogger("test");
    Log4Json layout = new Log4Json();
    WriterAppender appender = new WriterAppender(layout, writer);
    logger.addAppender(appender);
    return logger;
  }

  /**
   * This test logger avoids integrating with the main runtimes Logger hierarchy
   * in ways the reader does not want to know.
   */
  private static class TestLogger extends Logger {
    private TestLogger(String name, LoggerRepository repo) {
      super(name);
      repository = repo;
      setLevel(Level.INFO);
    }

  }

  public static class TestLoggerRepository implements LoggerRepository {
    @Override
    public void addHierarchyEventListener(HierarchyEventListener listener) {
    }

    @Override
    public boolean isDisabled(int level) {
      return false;
    }

    @Override
    public void setThreshold(Level level) {
    }

    @Override
    public void setThreshold(String val) {
    }

    @Override
    public void emitNoAppenderWarning(Category cat) {
    }

    @Override
    public Level getThreshold() {
      return Level.ALL;
    }

    @Override
    public Logger getLogger(String name) {
      return new TestLogger(name, this);
    }

    @Override
    public Logger getLogger(String name, LoggerFactory factory) {
      return new TestLogger(name, this);
    }

    @Override
    public Logger getRootLogger() {
      return new TestLogger("root", this);
    }

    @Override
    public Logger exists(String name) {
      return null;
    }

    @Override
    public void shutdown() {
    }

    @Override
    public Enumeration getCurrentLoggers() {
      return new Vector().elements();
    }

    @Override
    public Enumeration getCurrentCategories() {
      return new Vector().elements();
    }

    @Override
    public void fireAddAppenderEvent(Category logger, Appender appender) {
    }

    @Override
    public void resetConfiguration() {
    }
  }
}
