/**
 * 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.solr.servlet;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.Logger;


/**
 * Admin JDK Logger level report and selection servlet.
 *
 * @version $Id: LogLevelSelection.java 1065312 2011-01-30 16:08:25Z rmuir $
 * @since solr 1.3
 */
public final class LogLevelSelection extends HttpServlet {
  @Override
  public void init() throws ServletException {
  }

  /**
   * Processes an HTTP GET request and changes the logging level as
   * specified.
   */
  @Override
  public void doGet(HttpServletRequest request,
                    HttpServletResponse response)
          throws IOException, ServletException {
    // Output page

    response.setContentType("text/html");
    PrintWriter out = response.getWriter();
    out.write("<html><head>\n");
    out.write("<title>Solr Admin: JDK Log Level Selector</title>\n");
    out.write("<link rel=\"stylesheet\" type=\"text/css\" href=\"solr-admin.css\" />");
    out.write("</head><body>\n");
    out.write("<a href=\".\"><img border=\"0\" align=\"right\" height=\"78\" width=\"142\" src=\"solr_small.png\" alt=\"Solr\"></a>");
    out.write("<h1>JDK Log Level Selector</h1>");

    out.write("<p>Below is the complete JDK Log hierarchy with " +
            "intermediate logger/categories synthesized.  " +
            "The effective logging level is shown to the " +
            "far right. If a logger has unset level, then " +
            "the effective level is that of the nearest ancestor " +
            "with a level setting.  Note that this only shows " +
            "JDK Log levels.</p>\n");

    out.write("<form method='POST'>\n");

    out.write("<input type='submit' name='submit' value='set' " +
            "class='button'>\n");
    out.write("<input type='submit' name='submit' value='cancel' " +
            "class='button'>\n");
    out.write("<br><br>\n");

    out.write("<table cellspacing='2' cellpadding='2'>");

    out.write("<tr bgcolor='#CCCCFF'>" +
            "<th align=left>Logger/Category name<br>" +
            "<th colspan=9>Level</th>" +
            "</tr><tr bgcolor='#CCCCFF'>" +
            "<td bgcolor='#AAAAAA'>" +
            "(Dark rows don't yet exist.)</td>");

    for (int j = 0; j < LEVELS.length; ++j) {
      out.write("<th align=left>");
      if (LEVELS[j] != null) out.write(LEVELS[j].toString());
      else out.write("unset");
      out.write("</th>");
    }
    out.write("<th align=left>Effective</th>\n");
    out.write("</tr>\n");

    Iterator iWrappers = buildWrappers().iterator();
    while (iWrappers.hasNext()) {

      LogWrapper wrapper = (LogWrapper) iWrappers.next();

      out.write("<tr");
      if (wrapper.logger == null) {
        out.write(" bgcolor='#AAAAAA'");
      }
      //out.write( ( wrapper.logger != null ) ? "#DDDDDD" : "#AAAAAA" );
      out.write("><td>");
      if ("".equals(wrapper.name)) {
        out.write("root");
      } else {
        out.write(wrapper.name);
      }
      out.write("</td>\n");
      for (int j = 0; j < LEVELS.length; ++j) {
        out.write("<td align=center>");
        if (!wrapper.name.equals("root") ||
                (LEVELS[j] != null)) {
          out.write("<input type='radio' name='");
          if ("".equals(wrapper.name)) {
            out.write("root");
          } else {
            out.write(wrapper.name);
          }
          out.write("' value='");
          if (LEVELS[j] != null) out.write(LEVELS[j].toString());
          else out.write("unset");
          out.write('\'');
          if (LEVELS[j] == wrapper.level()) out.write(" checked");
          out.write('>');
        }
        out.write("</td>\n");
      }
      out.write("<td align=center>");
      if (wrapper.logger != null) {
        out.write(getEffectiveLevel(wrapper.logger).toString());
      }
      out.write("</td></tr>\n");
    }
    out.write("</table>\n");

    out.write("<br>\n");
    out.write("<input type='submit' name='submit' value='set' " +
            "class='button'>\n");
    out.write("<input type='submit' name='submit' value='cancel' " +
            "class='button'>\n");

    out.write("</form>\n");

    out.write("</body></html>\n");
  }


  @Override
  public void doPost(HttpServletRequest request,
                     HttpServletResponse response)
          throws IOException, ServletException {
    if (request.getParameter("submit").equals("set")) {

      Map paramMap = request.getParameterMap();

      Iterator iParams = paramMap.entrySet().iterator();
      while (iParams.hasNext()) {
        Map.Entry p = (Map.Entry) iParams.next();
        String name = (String) p.getKey();
        String value = ((String[]) p.getValue())[0];

        if (name.equals("submit")) continue;
        Logger logger;
        LogManager logManager = LogManager.getLogManager();
        if ("root".equals(name)) {
          logger = logManager.getLogger("");
        } else logger = logManager.getLogger(name);

        if ("unset".equals(value)) {
          if ((logger != null) && (logger.getLevel() != null)) {
            logger.setLevel(null);
            log.info("Unset log level on '" + name + "'.");
          }
        } else {
          Level level = Level.parse(value);
          if (logger == null) logger = Logger.getLogger(name);
          if (logger.getLevel() != level) {
            logger.setLevel(level);
            log.info("Set '" + name + "' to " +
                    level + " level.");
          }
        }
      }
    } else {
      log.fine("Selection form cancelled");
    }

    // Redirect back to standard get page.
    response.sendRedirect(request.getRequestURI());
  }


  private Collection buildWrappers() {
    // Use tree to get sorted results
    SortedSet<LogWrapper> roots = new TreeSet<LogWrapper>();

    roots.add(LogWrapper.ROOT);

    LogManager logManager = LogManager.getLogManager();

    Enumeration<String> loggerNames = logManager.getLoggerNames();
    while (loggerNames.hasMoreElements()) {
      String name = loggerNames.nextElement();
      Logger logger = Logger.getLogger(name);
      LogWrapper wrapper = new LogWrapper(logger);
      roots.remove(wrapper); // Make sure add occurs
      roots.add(wrapper);

      while (true) {
        int dot = name.lastIndexOf(".");
        if (dot < 0) break;
        name = name.substring(0, dot);
        roots.add(new LogWrapper(name)); // if not already
      }
    }

    return roots;
  }

  private Level getEffectiveLevel(Logger logger) {
    Level level = logger.getLevel();
    if (level != null) {
      return level;
    }
    for (Level l : LEVELS) {
      if (l == null) {
        // avoid NPE
        continue;
      }
      if (logger.isLoggable(l)) {
        // return first level loggable
        return l;
      }
    }
    return Level.OFF;
  }

  private static class LogWrapper
          implements Comparable {
    public static LogWrapper ROOT =
            new LogWrapper(LogManager.getLogManager().getLogger(""));

    public LogWrapper(Logger logger) {
      this.logger = logger;
      this.name = logger.getName();
    }

    public LogWrapper(String name) {
      this.name = name;
    }


    public int compareTo(Object other) {
      if (this.equals(other)) return 0;
      if (this == ROOT) return -1;
      if (other == ROOT) return 1;

      return name.compareTo(((LogWrapper) other).name);
    }

    @Override
    public boolean equals(Object obj) {
      if (this == obj)
        return true;
      if (obj == null)
        return false;
      if (getClass() != obj.getClass())
        return false;
      LogWrapper other = (LogWrapper) obj;
      if (name == null) {
        if (other.name != null)
          return false;
      } else if (!name.equals(other.name))
        return false;
      return true;
    }
    
    @Override
    public int hashCode() {
      final int prime = 31;
      int result = 1;
      result = prime * result + ((name == null) ? 0 : name.hashCode());
      return result;
    }

    public Level level() {
      if (logger != null) return logger.getLevel();
      return null;
    }

    public Logger logger = null;
    public String name;
  }

  private static Level[] LEVELS = {
          null, // aka unset
          Level.FINEST,
          Level.FINE,
          Level.CONFIG,
          Level.INFO,
          Level.WARNING,
          Level.SEVERE,
          Level.OFF
          // Level.ALL -- ignore.  It is useless.
  };

  private Logger log = Logger.getLogger(getClass().getName());
}
