/**
 * 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.gaffer

import ch.qos.logback.classic.LoggerContext
import org.junit.Before
import org.junit.Test

import javax.management.InstanceNotFoundException
import javax.management.ObjectName
import java.lang.management.ManagementFactory

import static org.junit.Assert.*

import ch.qos.logback.classic.turbo.TurboFilter
import ch.qos.logback.classic.turbo.ReconfigureOnChangeFilter
import ch.qos.logback.classic.Level
import ch.qos.logback.core.testUtil.CoreTestConstants
import ch.qos.logback.core.testUtil.RandomUtil
import ch.qos.logback.core.status.testUtil.StatusChecker
import ch.qos.logback.classic.Logger
import ch.qos.logback.core.Appender
import ch.qos.logback.core.helpers.NOPAppender
import ch.qos.logback.core.ConsoleAppender
import ch.qos.logback.core.encoder.LayoutWrappingEncoder
import ch.qos.logback.classic.PatternLayout
import ch.qos.logback.core.util.FileSize
import ch.qos.logback.core.util.StatusPrinter
import ch.qos.logback.classic.net.SMTPAppender
import ch.qos.logback.core.rolling.RollingFileAppender
import ch.qos.logback.core.rolling.TimeBasedRollingPolicy
import ch.qos.logback.classic.encoder.PatternLayoutEncoder
import ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP
import ch.qos.logback.core.joran.action.TimestampAction

/**
 * @author Ceki G&uuml;c&uuml;
 */
class ConfigurationDelegateTest {

  LoggerContext context = new LoggerContext()
  ConfigurationDelegate configurationDelegate = new ConfigurationDelegate();
  StatusChecker statusChecker = new StatusChecker(context)
  int diff = RandomUtil.getPositiveInt();

  String randomOutputDir = CoreTestConstants.OUTPUT_DIR_PREFIX + diff + "/";

  @Before
  void setUp() {
    context.name = "ConfigurationDelegateTest"
    configurationDelegate.context = context;
  }

  @Test
  void contextAwareMixin() {
    configurationDelegate.addInfo("smoke")
    assertTrue(statusChecker.containsMatch("smoke"))
  }

  @Test
  void scan() {
    configurationDelegate.scan("10seconds")
    assertTrue(statusChecker.containsMatch("Setting ReconfigureOnChangeTask"))
  }

  @Test
  void timestamp() {
    String result = configurationDelegate.timestamp("yyyy")
    long year = Calendar.getInstance().get(Calendar.YEAR);
    assertEquals(year.toString(), result)
  }

  @Test
  void timestampWithContextBirthAsReference() {
    String result = configurationDelegate.timestamp("yyyy", context.birthTime)
    long year = Calendar.getInstance().get(Calendar.YEAR);
    assertEquals(year.toString(), result)
  }


  @Test
  void loggerWithoutName() {
    configurationDelegate.logger("", Level.DEBUG)
    assertTrue(statusChecker.containsMatch("No name attribute for logger"))
  }

  @Test
  void loggerSetLevel() {
    configurationDelegate.logger("setLevel" + diff, Level.INFO)
    Logger smokeLogger = context.getLogger("setLevel" + diff);
    assertEquals(Level.INFO, smokeLogger.level)
  }


  @Test
  void loggerAppenderRef() {
    Appender fooAppender = new NOPAppender();
    fooAppender.name = "FOO"
    configurationDelegate.appenderList = [fooAppender]
    configurationDelegate.logger("test" + diff, Level.INFO, ["FOO"])
    Logger logger = context.getLogger("test" + diff);
    assertEquals(Level.INFO, logger.level)
    assertEquals(fooAppender, logger.getAppender("FOO"))
  }

  @Test
  void loggerAdditivity() {
    Appender fooAppender = new NOPAppender();
    fooAppender.name = "FOO"
    configurationDelegate.appenderList = [fooAppender]
    configurationDelegate.logger("test" + diff, Level.INFO, ["FOO"], false)
    Logger logger = context.getLogger("test" + diff);
    assertEquals(Level.INFO, logger.level)
    assertEquals(fooAppender, logger.getAppender("FOO"))
    assertEquals(false, logger.additive)
  }

  @Test
  void loggerAdditivittWithEmptyList() {
    configurationDelegate.logger("test" + diff, Level.INFO, [], false)
    Logger logger = context.getLogger("test" + diff);
    assertEquals(Level.INFO, logger.level)
    assertEquals(null, logger.getAppender("FOO"))
    assertEquals(false, logger.additive)
  }

  @Test
  void root_LEVEL() {
    configurationDelegate.root(Level.ERROR)
    Logger root = context.getLogger(Logger.ROOT_LOGGER_NAME);
    assertEquals(Level.ERROR, root.level)
    assertEquals(null, root.getAppender("FOO"))
  }

  @Test
  void root_WithList() {
    Appender fooAppender = new NOPAppender();
    fooAppender.name = "FOO"
    configurationDelegate.appenderList = [fooAppender]
    configurationDelegate.root(Level.WARN, ["FOO"])
    Logger root = context.getLogger(Logger.ROOT_LOGGER_NAME);
    assertEquals(Level.WARN, root.level)
    assertEquals(fooAppender, root.getAppender("FOO"))
  }

  @Test
  void appender0() {
    configurationDelegate.appender("A", NOPAppender);
    Appender back = configurationDelegate.appenderList.find {it.name = "A"}
    assertNotNull(back)
    assertEquals("A", back.name)
  }

  @Test
  void appender1() {
    configurationDelegate.appender("C", ConsoleAppender) {
      target = "System.err"
    }
    Appender back = configurationDelegate.appenderList.find {it.name = "C"}
    assertNotNull(back)
    assertEquals("C", back.name)
    assertEquals("System.err", back.target)
  }


  @Test
  void appenderWithEncoder() {
    configurationDelegate.appender("C", ConsoleAppender) {
      encoder(LayoutWrappingEncoder) {
        layout(PatternLayout) {
          pattern = "%m%n"
        }
      }
    }
    Appender back = configurationDelegate.appenderList.find {it.name = "C"}
    assertNotNull(back)
    assertEquals("C", back.name)
    ConsoleAppender ca = back
    assertNotNull(ca.encoder)
    assertNotNull(ca.encoder.layout)
    PatternLayout layout = ca.encoder.layout
    assertEquals("%m%n", layout.pattern)

  }

  @Test
  void appenderSMTP() {
    configurationDelegate.appender("SMTP", SMTPAppender) {
      to = "a"
      to = "b"
      layout(PatternLayout) {
        pattern = "%m%n"
      }
    }
    //StatusPrinter.print context
    Appender back = configurationDelegate.appenderList.find {it.name = "SMTP"}
    assertNotNull(back)
    assertEquals("SMTP", back.name)
    SMTPAppender sa = back
    PatternLayout layout = sa.layout
    assertEquals("%m%n", layout.pattern)

    assertEquals(["a%nopex", "b%nopex"], sa.getToAsListOfString().sort());
  }

  // test parent injection

  @Test
  void appenderRolling() {

    String logFile = randomOutputDir + "log.txt";

    configurationDelegate.appender("ROLLING", RollingFileAppender) {
      file = logFile
      rollingPolicy(TimeBasedRollingPolicy) {
        fileNamePattern = randomOutputDir + "log.%d{yyyy-MM}.log.zip"
      }
      encoder(PatternLayoutEncoder) {
        pattern = '%msg%n'
      }
    }
    // StatusPrinter.print context
    RollingFileAppender back = configurationDelegate.appenderList.find {it.name = "ROLLING"}
    assertNotNull(back)
    assertEquals(logFile, back.rollingPolicy.getParentsRawFileProperty())
  }


  // See LOGBACK-458
  @Test
  void withSizeAndTimeBasedFNATP() {
      withSizeAndTimeBasedFNATP(false);
  }

  // See LOGBACK-1232
  @Test
  void withSizeAndTimeBasedFNATP_AsString() {
      withSizeAndTimeBasedFNATP(true);
  }
  
  void withSizeAndTimeBasedFNATP(boolean asString) {
    String logFile = randomOutputDir + "log.txt";
    configurationDelegate.appender("ROLLING", RollingFileAppender) {
      file = logFile
      rollingPolicy(TimeBasedRollingPolicy) {
        fileNamePattern = "mylog-%d{yyyy-MM-dd}.%i.txt"
        timeBasedFileNamingAndTriggeringPolicy(SizeAndTimeBasedFNATP) {
          if(asString)   
            maxFileSize = "100MB"
          else 
           maxFileSize = FileSize.valueOf("100MB")
        }
      }
      encoder(PatternLayoutEncoder) {
        pattern = "%msg%n"
      }
    }
    RollingFileAppender back = configurationDelegate.appenderList.find {it.name = "ROLLING"}
    StatusPrinter.print(context)
    assertNotNull(back)
    assertEquals(logFile, back.rollingPolicy.getParentsRawFileProperty())
    assertTrue(back.rollingPolicy.timeBasedFileNamingAndTriggeringPolicy.isStarted())
  }

  @Test
  void jmxConfiguratorWithDefaults() {
    ObjectName name = new ObjectName(
            "ch.qos.logback.classic:Name=ConfigurationDelegateTest,Type=ch.qos.logback.classic.jmx.JMXConfigurator")
    try {
       ManagementFactory.platformMBeanServer.getObjectInstance(name)
       fail("Should not have found JMXConfigurator MBean")
    } catch (InstanceNotFoundException expected) {
    }
    configurationDelegate.jmxConfigurator()
    def mbean = ManagementFactory.platformMBeanServer.getObjectInstance(name)
    assertNotNull(mbean)
  }

    @Test
    void jmxConfiguratorWithNonDefaultContextName() {
        ObjectName name = new ObjectName(
                "ch.qos.logback.classic:Name=CustomName,Type=ch.qos.logback.classic.jmx.JMXConfigurator")
        try {
            ManagementFactory.platformMBeanServer.getObjectInstance(name)
            fail("Should not have found JMXConfigurator MBean")
        } catch (InstanceNotFoundException expected) {
        }
        configurationDelegate.jmxConfigurator("CustomName")
        def mbean = ManagementFactory.platformMBeanServer.getObjectInstance(name)
        assertNotNull(mbean)
    }

    @Test
    void jmxConfiguratorWithNonDefaultObjectName() {
        ObjectName name = new ObjectName("customDomain:Name=JMX")
        try {
            ManagementFactory.platformMBeanServer.getObjectInstance(name)
            fail("Should not have found JMXConfigurator MBean")
        } catch (InstanceNotFoundException expected) {
        }
        configurationDelegate.jmxConfigurator("customDomain:Name=JMX")
        def mbean = ManagementFactory.platformMBeanServer.getObjectInstance(name)
        assertNotNull(mbean)
    }

}
