/**
 * Logback: the reliable, generic, fast and flexible logging framework.
 * Copyright (C) 1999-2010, 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.Level
import ch.qos.logback.classic.Logger
import ch.qos.logback.classic.LoggerContext
import ch.qos.logback.classic.jmx.JMXConfigurator
import ch.qos.logback.classic.jmx.MBeanUtil
import ch.qos.logback.classic.net.ReceiverBase
import ch.qos.logback.classic.turbo.ReconfigureOnChangeFilter
import ch.qos.logback.classic.turbo.TurboFilter
import ch.qos.logback.core.Appender
import ch.qos.logback.core.CoreConstants
import ch.qos.logback.core.spi.ContextAwareBase
import ch.qos.logback.core.status.StatusListener
import ch.qos.logback.core.util.CachingDateFormatter
import ch.qos.logback.core.util.Duration
import ch.qos.logback.core.spi.LifeCycle
import ch.qos.logback.core.spi.ContextAware

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

/**
 * @author Ceki G&uuml;c&uuml;
 */

public class ConfigurationDelegate extends ContextAwareBase {

  List<Appender> appenderList = [];

  Object getDeclaredOrigin() {
    return this;
  }




  void scan(String scanPeriodStr = null) {
    ReconfigureOnChangeFilter rocf = new ReconfigureOnChangeFilter();
    rocf.setContext(context);
    if (scanPeriodStr) {
      try {
        Duration duration = Duration.valueOf(scanPeriodStr);
        rocf.setRefreshPeriod(duration.getMilliseconds());
        addInfo("Setting ReconfigureOnChangeFilter scanning period to "
                + duration);
      } catch (NumberFormatException nfe) {
        addError("Error while converting [" + scanAttrib + "] to long", nfe);
      }
    }
    rocf.start();
    addInfo("Adding ReconfigureOnChangeFilter as a turbo filter");
    context.addTurboFilter(rocf);
  }

  void statusListener(Class listenerClass) {
    StatusListener statusListener = listenerClass.newInstance()
    context.statusManager.add(statusListener)
    if(statusListener instanceof ContextAware) {
      ((ContextAware) statusListener).setContext(context);
    }
    if(statusListener instanceof LifeCycle) {
      ((LifeCycle) statusListener).start();
    }
    addInfo("Added status listener of type [${listenerClass.canonicalName}]");
  }

  void conversionRule(String conversionWord, Class converterClass) {
    String converterClassName = converterClass.getName();

    Map<String, String> ruleRegistry = (Map) context.getObject(CoreConstants.PATTERN_RULE_REGISTRY);
    if (ruleRegistry == null) {
      ruleRegistry = new HashMap<String, String>();
      context.putObject(CoreConstants.PATTERN_RULE_REGISTRY, ruleRegistry);
    }
    // put the new rule into the rule registry
    addInfo("registering conversion word " + conversionWord + " with class [" + converterClassName + "]");
    ruleRegistry.put(conversionWord, converterClassName);
  }

  void root(Level level, List<String> appenderNames = []) {
    if (level == null) {
      addError("Root logger cannot be set to level null");
    } else {
      logger(org.slf4j.Logger.ROOT_LOGGER_NAME, level, appenderNames);
    }
  }

  void logger(String name, Level level, List<String> appenderNames = [], Boolean additivity = null) {
    if (name) {
      Logger logger = ((LoggerContext) context).getLogger(name);
      addInfo("Setting level of logger [${name}] to " + level);
      logger.level = level;

      for (aName in appenderNames) {
        Appender appender = appenderList.find { it -> it.name == aName };
        if (appender != null) {
          addInfo("Attaching appender named [${aName}] to " + logger);
          logger.addAppender(appender);
        } else {
          addError("Failed to find appender named [${aName}]");
        }
      }

      if (additivity != null) {
        logger.additive = additivity;
      }
    } else {
      addInfo("No name attribute for logger");
    }
  }

  void appender(String name, Class clazz, Closure closure = null) {
    addInfo("About to instantiate appender of type [" + clazz.name + "]");
    Appender appender = clazz.newInstance();
    addInfo("Naming appender as [" + name + "]");
    appender.name = name
    appender.context = context
    appenderList.add(appender)
    if (closure != null) {
      AppenderDelegate ad = clazz.name == 'ch.qos.logback.classic.AsyncAppender' ? new AsyncAppenderDelegate(appender, appenderList) : new AppenderDelegate(appender);
      copyContributions(ad, appender)
      ad.context = context;
      closure.delegate = ad;
      closure.resolveStrategy = Closure.DELEGATE_FIRST
      closure();
    }
    try {
      appender.start()
    } catch (RuntimeException e) {
      addError("Failed to start apppender named [" + name + "]", e)
    }
  }

  void receiver(String name, Class aClass, Closure closure = null) {
    addInfo("About to instantiate receiver of type [" + clazz.name + "]");
    ReceiverBase receiver = aClass.newInstance();
    receiver.context = context;
    if(closure != null) {
      ComponentDelegate componentDelegate = new ComponentDelegate(receiver);
      componentDelegate.context = context;
      closure.delegate = componentDelegate;
      closure.resolveStrategy = Closure.DELEGATE_FIRST
      closure();
    }
    try {
      receiver.start()
    } catch (RuntimeException e) {
      addError("Failed to start receiver of type [" + aClass.getName() + "]", e)
    }
  }

  private void copyContributions(AppenderDelegate appenderDelegate, Appender appender) {
    if (appender instanceof ConfigurationContributor) {
      ConfigurationContributor cc = (ConfigurationContributor) appender;
      cc.getMappings().each() { oldName, newName ->
        appenderDelegate.metaClass."${newName}" = appender.&"$oldName"
      }
    }
  }

  void turboFilter(Class clazz, Closure closure = null) {
    addInfo("About to instantiate turboFilter of type [" + clazz.name + "]");
    TurboFilter turboFilter = clazz.newInstance();
    turboFilter.context = context

    if (closure != null) {
      ComponentDelegate componentDelegate = new ComponentDelegate(turboFilter);
      componentDelegate.context = context;
      closure.delegate = componentDelegate;
      closure.resolveStrategy = Closure.DELEGATE_FIRST
      closure();
    }
    turboFilter.start();
    addInfo("Adding aforementioned turbo filter to context");
    context.addTurboFilter(turboFilter)
  }

  String timestamp(String datePattern, long timeReference = -1) {
    long now = -1;

    if (timeReference == -1) {
      addInfo("Using current interpretation time, i.e. now, as time reference.");
      now = System.currentTimeMillis()
    } else {
      now = timeReference
      addInfo("Using " + now + " as time reference.");
    }
    CachingDateFormatter sdf = new CachingDateFormatter(datePattern);
    sdf.format(now)
  }

    /**
     * Creates and registers a {@link JMXConfigurator} with the platform MBean Server.
     * Allows specifying a custom context name to derive the used ObjectName, or a complete
     * ObjectName string representation to determine your own (the syntax automatically determines the intent).
     *
     * @param name custom context name or full ObjectName string representation (defaults to null)
     */
  void jmxConfigurator(String name = null) {
    def objectName = null
    def contextName = context.name
    if (name != null) {
      // check if this is a valid ObjectName
      try {
        objectName = new ObjectName(name)
      } catch (MalformedObjectNameException e) {
        contextName = name
      }
    }
    if (objectName == null) {
      def objectNameAsStr = MBeanUtil.getObjectNameFor(contextName, JMXConfigurator.class)
      objectName = MBeanUtil.string2ObjectName(context, this, objectNameAsStr)
      if (objectName == null) {
        addError("Failed to construct ObjectName for [${objectNameAsStr}]")
        return
      }
    }

    def platformMBeanServer = ManagementFactory.platformMBeanServer
    if (!MBeanUtil.isRegistered(platformMBeanServer, objectName)) {
      JMXConfigurator jmxConfigurator = new JMXConfigurator((LoggerContext) context, platformMBeanServer, objectName)
      try {
        platformMBeanServer.registerMBean(jmxConfigurator, objectName)
      } catch (all) {
        addError("Failed to create mbean", all)
      }
    }
  }

}
