/**
 * Copyright 2019 Anthony Trinh
 *
 * Licensed 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 ch.qos.logback.classic.html;

import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertTrue;

import java.io.ByteArrayInputStream;
import java.util.List;

import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.xml.sax.EntityResolver;

import ch.qos.logback.classic.ClassicTestConstants;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.joran.JoranConfigurator;
import ch.qos.logback.classic.spi.DummyThrowableProxy;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.classic.spi.LoggingEvent;
import ch.qos.logback.classic.spi.StackTraceElementProxy;
import ch.qos.logback.classic.spi.ThrowableProxy;
import ch.qos.logback.core.CoreConstants;
import ch.qos.logback.core.joran.spi.JoranException;
import ch.qos.logback.core.testUtil.StringListAppender;
import ch.qos.logback.core.util.StatusPrinter;

@RunWith(RobolectricTestRunner.class)
public class HTMLLayoutTest {

  LoggerContext lc;
  Logger root;
  HTMLLayout layout;

  @Before
  public void setUp() throws Exception {
    lc = new LoggerContext();
    lc.setName("default");

    layout = new HTMLLayout();
    layout.setThrowableRenderer(new DefaultThrowableRenderer());
    layout.setContext(lc);
    layout.setPattern("%level%thread%msg");
    layout.start();

    root = lc.getLogger(Logger.ROOT_LOGGER_NAME);

  }

  @After
  public void tearDown() throws Exception {
    lc = null;
    layout = null;
  }

  @Test
  public void testHeader() throws Exception {
    String header = layout.getFileHeader();
    // System.out.println(header);

    Document doc = parseOutput(header + "</body></html>");
    Element rootElement = doc.getRootElement();
    assertNotNull(rootElement.element("body"));
  }

  @SuppressWarnings("unchecked")
  @Test
  public void testPresentationHeader() throws Exception {
    String header = layout.getFileHeader();
    String presentationHeader = layout.getPresentationHeader();
    header = header + presentationHeader;
    // System.out.println(header);

    Document doc = parseOutput(header + "</table></body></html>");
    Element rootElement = doc.getRootElement();
    Element bodyElement = rootElement.element("body");
    Element tableElement = bodyElement.element("table");
    Element trElement = tableElement.element("tr");
    List<Element> elementList = trElement.elements();
    assertEquals("Level", elementList.get(0).getText());
    assertEquals("Thread", elementList.get(1).getText());
    assertEquals("Message", elementList.get(2).getText());
  }

  @Test
  public void testAppendThrowable() throws Exception {
    StringBuilder buf = new StringBuilder();
    DummyThrowableProxy tp = new DummyThrowableProxy();
    tp.setClassName("test1");
    tp.setMessage("msg1");
    
    StackTraceElement ste1 = new StackTraceElement("c1", "m1", "f1", 1);
    StackTraceElement ste2 = new StackTraceElement("c2", "m2", "f2", 2);
    
    StackTraceElementProxy[] stepArray = { new StackTraceElementProxy(ste1),
        new StackTraceElementProxy(ste2) };
    tp.setStackTraceElementProxyArray(stepArray);
    DefaultThrowableRenderer renderer = (DefaultThrowableRenderer) layout
        .getThrowableRenderer();

    renderer.render(buf, tp);
    System.out.println(buf.toString());
    String[] result = buf.toString().split(CoreConstants.LINE_SEPARATOR);
    System.out.println(result[0]);
    assertEquals("test1: msg1", result[0]);
    assertEquals(DefaultThrowableRenderer.TRACE_PREFIX + "at c1.m1(f1:1)", result[1]);
  }

  @Test
  public void testDoLayout() throws Exception {
    ILoggingEvent le = createLoggingEvent();

    String result = layout.getFileHeader();
    result += layout.getPresentationHeader();
    result += layout.doLayout(le);
    result += layout.getPresentationFooter();
    result += layout.getFileFooter();

    Document doc = parseOutput(result);
    Element rootElement = doc.getRootElement();
    rootElement.toString();

    // the rest of this test is very dependent of the output generated
    // by HTMLLayout. Given that the XML parser already verifies
    // that the result conforms to xhtml-strict, we may want to
    // skip the assertions below. However, the assertions below are another
    // *independent* way to check the output format.

    // head, body
    assertEquals(2, rootElement.elements().size());
    Element bodyElement = (Element) rootElement.elements().get(1);
    Element tableElement = (Element) bodyElement.elements().get(3);
    assertEquals("table", tableElement.getName());
    Element trElement = (Element) tableElement.elements().get(1);
    {
      Element tdElement = (Element) trElement.elements().get(0);
      assertEquals("DEBUG", tdElement.getText());
    }
    {
      Element tdElement = (Element) trElement.elements().get(1);
      String regex = ClassicTestConstants.NAKED_MAIN_REGEX;
      System.out.println(tdElement.getText());
      assertTrue(tdElement.getText().matches(regex));
    }
    {
      Element tdElement = (Element) trElement.elements().get(2);
      assertEquals("test message", tdElement.getText());
    }
  }

  @SuppressWarnings("unchecked")
  @Test
  public void layoutWithException() throws Exception {
    layout.setPattern("%level %thread %msg %ex");
    LoggingEvent le = createLoggingEvent();
    le.setThrowableProxy(new ThrowableProxy(new Exception("test Exception")));
    String result = layout.doLayout(le);

    String stringToParse = layout.getFileHeader();
    stringToParse = stringToParse + layout.getPresentationHeader();
    stringToParse += result;
    stringToParse += "</table></body></html>";

    // System.out.println(stringToParse);

    Document doc = parseOutput(stringToParse);
    Element rootElement = doc.getRootElement();
    Element bodyElement = rootElement.element("body");
    Element tableElement = bodyElement.element("table");
    List<Element> trElementList = tableElement.elements();
    Element exceptionRowElement = trElementList.get(2);
    Element exceptionElement = exceptionRowElement.element("td");

    assertEquals(3, tableElement.elements().size());
    assertTrue(exceptionElement.getText().contains(
        "java.lang.Exception: test Exception"));
  }

  @Test
  @Ignore
  public void rawLimit() throws Exception {
    StringBuilder sb = new StringBuilder();
    String header = layout.getFileHeader();
    assertTrue(header
        .startsWith("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">"));
    sb.append(header);
    sb.append(layout.getPresentationHeader());
    for (int i = 0; i < CoreConstants.TABLE_ROW_LIMIT * 3; i++) {
      sb.append(layout.doLayout(new LoggingEvent(this.getClass().getName(),
          root, Level.DEBUG, "test message" + i, null, null)));
    }
    sb.append(layout.getPresentationFooter());
    sb.append(layout.getFileFooter());
    // check that the output adheres to xhtml-strict.dtd
    parseOutput(sb.toString());
  }

  private LoggingEvent createLoggingEvent() {
    return new LoggingEvent(this.getClass().getName(), root,
        Level.DEBUG, "test message", null, null);
  }

  Document parseOutput(String output) throws Exception {
    EntityResolver resolver = new XHTMLEntityResolver();
    SAXReader reader = new SAXReader();
    reader.setValidation(true);
    reader.setEntityResolver(resolver);
    return reader.read(new ByteArrayInputStream(output.getBytes()));
  }

  void configure(String file) throws JoranException {
    JoranConfigurator jc = new JoranConfigurator();
    jc.setContext(lc);
    jc.doConfigure(file);
  }

  @Test
  public void testConversionRuleSupportInHtmlLayout() throws JoranException {
    configure(ClassicTestConstants.JORAN_INPUT_PREFIX
        + "conversionRule/htmlLayout0.xml");
   
    root.getAppender("LIST");
    String msg = "Simon says";
    root.debug(msg);
    StringListAppender<ILoggingEvent> sla = (StringListAppender<ILoggingEvent>) root
        .getAppender("LIST");
    assertNotNull(sla);
    StatusPrinter.print(lc);
    assertEquals(1, sla.strList.size());
    assertFalse(sla.strList.get(0).contains("PARSER_ERROR"));
  }
}
