/*
 * Copyright (C) 2021 - 2025 ScalAgent Distributed Technologies
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
 * USA.
 *
 * Original developer: ScalAgent Distributed Technologies
 */
package org.objectweb.util.monolog.jul;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.logging.Formatter;
import java.util.logging.LogManager;

class LogFormatter extends Formatter implements ExtendedFormatter {
  private static final String FORMAT_PROP_KEY = "org.objectweb.util.monolog.jul.LogFormatter.format";
  private static final String OLD_FORMAT_PROP_KEY = "org.objectweb.util.monolog.api.LogFormatter.format";
  private static final String DEFAULT_FORMAT = "%1$s %7$s %8$s [%4$s.%5$s(%6$s)] %12$s: %10$s%13$s%n";

  private String format;
  
  public LogFormatter() {
    format = LogManager.getLogManager().getProperty(FORMAT_PROP_KEY);
    if (format == null) {
      // Try to use old property name!
      format = LogManager.getLogManager().getProperty(OLD_FORMAT_PROP_KEY);
      if (format != null) {
        System.err.println("Property \"" + OLD_FORMAT_PROP_KEY + "\" is deprecated, you should use \"" + FORMAT_PROP_KEY + "\" instead.\n" +
            "The old property will be removed in future.");
      }
    }

    if (format != null) {
      try {
        // validate the user-defined format string
        validateFormat(format);
      } catch (IllegalArgumentException e) {
        // illegal syntax; fall back to the default format
        System.err.println("Bad format property, use default: " + e.getMessage());
        format = DEFAULT_FORMAT;
      }
    } else {
      System.err.println("No format property, use default");
      format = DEFAULT_FORMAT;
    }
  }
  
  // TODO: Be careful, DateFormat is not thread-safe.
  private static final DateFormat df = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss.SSS");
  
//  private final void format3digits(StringBuilder strbuf, int i) {
//    if (i < 100) strbuf.append('0');
//    if (i < 10) strbuf.append('0');
//    strbuf.append(i);
//  }
//  
//  private final void format2digits(StringBuilder strbuf, int i) {
//    if (i < 10) strbuf.append('0');
//    strbuf.append(i);
//  }
//
//  private final void appendISO8601(StringBuilder sb, long millis) {
//    GregorianCalendar cal = new GregorianCalendar();
//    cal.setTimeInMillis(millis);
//    format2digits(sb, cal.get(Calendar.DAY_OF_MONTH));
//    sb.append('/');
//    format2digits(sb, cal.get(Calendar.MONTH) + 1);
//    sb.append('/');
//    sb.append(cal.get(Calendar.YEAR));
//    sb.append(' ');
//    format2digits(sb, cal.get(Calendar.HOUR_OF_DAY));
//    sb.append(':');
//    format2digits(sb, cal.get(Calendar.MINUTE));
//    sb.append(':');
//    format2digits(sb, cal.get(Calendar.SECOND));
//    sb.append('.');
//    format3digits(sb, cal.get(Calendar.MILLISECOND));
//  }

  /**
   * Format the given LogRecord.
   * <p>
   * The formatting can be customized by specifying the format string in the java.util.logging.SimpleFormatter.format property.
   * The given LogRecord will be formatted as if by calling:</p>
   * <pre>
   * <code>String.format</code></a>(format, date1, date2, source1, source2, method, line, logger, level1, level2, message, thread1, thread2, thrown);
   * </pre>
   * where the arguments are:<br>
   * <ol>
   * <ul>
   *   <li><code>format</code> - the java.util.Formatter format string specified in the corresponding format property or the default format.</li>
   *   <li><code>date1</code> - Date/Time in "dd/MM/yyyy hh:mm:ss.SSS" format representing event time of the log record.</li>
   *   <li><code>date2</code> -  a Date object representing event time of the log record.</li>
   *   <li><code>source1</code> - Full class name of the caller, if available.</li>
   *   <li><code>source2</code> - Class name of the caller, if available.</li>
   *   <li><code>method</code> - Method name of the caller, if available.</li>
   *   <li><code>line</code> - Line number of the caller, if available.</li>
   *   <li><code>logger</code> - the logger's name.</li>
   *   <li><code>level1</code> - the name of Level.</li>
   *   <li><code>level2</code> - the localized name of Level.</li>
   *   <li><code>message</code> - the formatted log message returned from the Formatter.formatMessage(LogRecord) method. It uses java.text
   *   formatting and does not use the java.util.Formatter format argument.</li>
   *   <li><code>thread1</code> - a unique Thread identifier</li>
   *   <li><code>thread2</code> - the thread name</li>
   *   <li><code>thrown</code> - a string representing the throwable associated with the log record and its backtrace beginning
   *   with a newline character, if any; otherwise, an empty string</li>
   * </ol>
   */
  @Override
  public String format(java.util.logging.LogRecord record) {
    // TODO (AF): Should synchronize this method (use of df static variable).
    Date date = new Date(record.getMillis());

    String source = "";
    String fullSource = record.getSourceClassName();
    if (fullSource == null) {
      fullSource = "";
    } else {
      int idx = fullSource.lastIndexOf('.');
      if (idx == -1)
        source = fullSource;
      else
        source = fullSource.substring(idx +1);
    }
    String method = record.getSourceMethodName();
    if (method == null) method = "";

    String message = formatMessage(record);
    String throwable = "";
    if (record.getThrown() != null) {
      StringWriter sw = new StringWriter();
      PrintWriter pw = new PrintWriter(sw);
      pw.println();
      record.getThrown().printStackTrace(pw);
      pw.close();
      throwable = sw.toString();
    }

    String threadName = "T" + record.getThreadID();
    String lineNumber = "";
    if (record instanceof LogRecord) {
      LogRecord r = (LogRecord) record;
      threadName = r.getThreadName();
      lineNumber = "" + r.getSourceLineNumber();
    }
    //    if (record.getParameters()[0] != null) {
    //      
    //    } else {
    //      threadName = "t" + record.getThreadID();
    //    }
    
    return String.format(format,
                         df.format(date),    // %1$ String
                         date,  // %2$ Date
                         fullSource,  // %3$ String
                         source,  // %4$ String
                         method,  // %5$ String
                         lineNumber,  // %6$ String
                         record.getLoggerName(),  // %7$ String
                         record.getLevel().getName(), // %8 String
                         record.getLevel().getLocalizedName(),  // %9$ String
                         message,  // %10$ String
                         record.getThreadID(),  // %11$ int
                         threadName,  // %12$ String
                         throwable);  // %13$ String
  }

  /**
   * This call allows to validate a format throwing an IllegalArgumentException if the format
   * is not valid.
   *  
   * @param f The format to validate.
   * @throws IllegalArgumentException if the format is not valid.
   */
  private void validateFormat(String f) throws IllegalArgumentException {
    String.format(format, new Date(), "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", 11, "12", "13");
  }

  @Override
  public void setFormat(String f) throws IllegalArgumentException {
    // validate the user-defined format string
    validateFormat(f);
    format = f;
  }
}
