/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Copyright 2006-2008 Sun Microsystems, Inc.
 */
package org.opends.server.util;



import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Iterator;
import java.util.TimeZone;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;

import org.opends.server.api.DirectoryThread;

import static org.opends.server.loggers.debug.DebugLogger.*;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.types.DebugLogLevel;
import static org.opends.server.util.ServerConstants.*;


/**
 * This class defines a thread that will wake up periodically, get the current
 * time, and store various representations of it.  Note that only limited
 * debugging will be performed in this class due to the frequency with which it
 * will be called.
 */
@org.opends.server.types.PublicAPI(
     stability=org.opends.server.types.StabilityLevel.UNCOMMITTED,
     mayInstantiate=false,
     mayExtend=false,
     mayInvoke=true)
public final class TimeThread
       extends DirectoryThread
{
  /**
   * The tracer object for the debug logger.
   */
  private static final DebugTracer TRACER = getTracer();



  /**
   * The singleton instance for this time thread.
   */
  private static final TimeThread threadInstance = new TimeThread();



  // The calendar holding the current time.
  private static GregorianCalendar calendar;

  // A set of arbitrary formatters that should be maintained by this time
  // thread.
  private static CopyOnWriteArrayList<SimpleDateFormat> userDefinedFormatters;

  // A set of abitrary formatted times, mapped from format string to the
  // corresponding formatted time representation.
  private static ConcurrentHashMap<String,String> userDefinedTimeStrings;

  // The date for this time thread.
  private static Date date;

  // The current time in HHmm form as an integer.
  private static int hourAndMinute;

  // The current time in milliseconds since the epoch.
  private static volatile long time;

  // The current time in nanoseconds.
  private static volatile long nanoTime;

  // The date formatter that will be used to obtain the generalized time.
  private static SimpleDateFormat generalizedTimeFormatter;

  // The date formatter that will be used to obtain the local timestamp.
  private static SimpleDateFormat localTimestampFormatter;

  // The date formatter that will be used to obtain the GMT timestamp.
  private static SimpleDateFormat gmtTimestampFormatter;

  // The timestamp for this time thread in the generalized time format.
  private static String generalizedTime;

  // The timestamp for this time thread in the local time zone.
  private static String localTimestamp;

  // The timestamp for this time thread in GMT.
  private static String gmtTimestamp;



  /**
   * Creates a new instance of this time thread and starts it.
   */
  private TimeThread()
  {
    super("Time Thread");

    setDaemon(true);

    userDefinedFormatters  = new CopyOnWriteArrayList<SimpleDateFormat>();
    userDefinedTimeStrings = new ConcurrentHashMap<String,String>();

    TimeZone utcTimeZone = TimeZone.getTimeZone(TIME_ZONE_UTC);

    generalizedTimeFormatter =
         new SimpleDateFormat(DATE_FORMAT_GENERALIZED_TIME);
    generalizedTimeFormatter.setTimeZone(utcTimeZone);

    gmtTimestampFormatter = new SimpleDateFormat(DATE_FORMAT_GMT_TIME);
    gmtTimestampFormatter.setTimeZone(utcTimeZone);

    localTimestampFormatter = new SimpleDateFormat(DATE_FORMAT_LOCAL_TIME);

    calendar        = new GregorianCalendar();
    date            = calendar.getTime();
    time            = date.getTime();
    nanoTime        = System.nanoTime();
    generalizedTime = generalizedTimeFormatter.format(date);
    localTimestamp  = localTimestampFormatter.format(date);
    gmtTimestamp    = gmtTimestampFormatter.format(date);
    hourAndMinute   = (calendar.get(Calendar.HOUR_OF_DAY) * 100) +
                      calendar.get(Calendar.MINUTE);

    start();
  }



  /**
   * Operates in a loop, getting the current time and then sleeping briefly
   * before checking again.
   */
  public void run()
  {
    while (true)
    {
      try
      {
        calendar        = new GregorianCalendar();
        date            = calendar.getTime();
        time            = date.getTime();
        nanoTime        = System.nanoTime();
        generalizedTime = generalizedTimeFormatter.format(date);
        localTimestamp  = localTimestampFormatter.format(date);
        gmtTimestamp    = gmtTimestampFormatter.format(date);
        hourAndMinute   = (calendar.get(Calendar.HOUR_OF_DAY) * 100) +
                          calendar.get(Calendar.MINUTE);

        for (SimpleDateFormat format : userDefinedFormatters)
        {
          userDefinedTimeStrings.put(format.toPattern(),
                                     format.format(date));
        }

        Thread.sleep(200);
      }
      catch (Exception e)
      {
        if (debugEnabled())
        {
          TRACER.debugCaught(DebugLogLevel.ERROR, e);
        }
      }
    }
  }



  /**
   * Retrieves a <CODE>Calendar</CODE> containing the time at the last update.
   *
   * @return  A <CODE>Calendar</CODE> containing the time at the last update.
   */
  public static Calendar getCalendar()
  {
    return calendar;
  }



  /**
   * Retrieves a <CODE>Date</CODE> containing the time at the last update.
   *
   * @return  A <CODE>Date</CODE> containing the time at the last update.
   */
  public static Date getDate()
  {
    return date;
  }



  /**
   * Retrieves the time in milliseconds since the epoch at the last update.
   *
   * @return  The time in milliseconds since the epoch at the last update.
   */
  public static long getTime()
  {
    return time;
  }

  /**
   * Retrieves the time in nanoseconds from the most precise available system
   * timer. The value retured represents nanoseconds since some fixed but
   * arbitrary time.
   *
   * @return The time in nanoseconds from some fixed but arbitrary time.
   */
  public static long getNanoTime()
  {
    return nanoTime;
  }



  /**
   * Retrieves a string containing a normalized representation of the current
   * time in a generalized time format.  The timestamp will look like
   * "20050101000000.000Z".
   *
   * @return  A string containing a normalized representation of the current
   *          time in a generalized time format.
   */
  public static String getGeneralizedTime()
  {
    return generalizedTime;
  }



  /**
   * Retrieves a string containing the current time in the local time zone.  The
   * timestamp format will look like "01/Jan/2005:00:00:00 -0600".
   *
   * @return  A string containing the current time in the local time zone.
   */
  public static String getLocalTime()
  {
    return localTimestamp;
  }



  /**
   * Retrieves a string containing the current time in GMT.  The timestamp will
   * look like "20050101000000Z".
   *
   * @return  A string containing the current time in GMT.
   */
  public static String getGMTTime()
  {
    return gmtTimestamp;
  }



  /**
   * Retrieves an integer containing the time in HHmm format at the last
   * update.  It will be calculated as "(hourOfDay*100) + minuteOfHour".
   *
   * @return  An integer containing the time in HHmm format at the last update.
   */
  public static int getHourAndMinute()
  {
    return hourAndMinute;
  }



  /**
   * Retrieves the current time formatted using the given format string.  The
   * first time this method is used with a given format string, it will be used
   * to create a formatter that will generate the time string.  That formatter
   * will then be put into a list so that it will be maintained automatically
   * for future use.
   *
   * @param  formatString  The string that defines the format of the time string
   *                       to retrieve.
   *
   * @return  The formatted time string.
   *
   * @throws  IllegalArgumentException  If the provided format string is
   *                                    invalid.
   */
  public static String getUserDefinedTime(String formatString)
         throws IllegalArgumentException
  {
    String timeString = userDefinedTimeStrings.get(formatString);

    if (timeString == null)
    {
      SimpleDateFormat formatter = new SimpleDateFormat(formatString);
      timeString = formatter.format(date);
      userDefinedTimeStrings.put(formatString, timeString);
      userDefinedFormatters.add(formatter);
    }

    return timeString;
  }



  /**
   * Removes the user-defined time formatter from this time thread so that it
   * will no longer be maintained.  This is a safe operation because if the
   * same format string is used for multiple purposes then it will be added back
   * the next time a time is requested with the given format.
   *
   * @param  formatString  The format string for the date formatter to remove.
   */
  public static void removeUserDefinedFormatter(String formatString)
  {
    Iterator<SimpleDateFormat> iterator = userDefinedFormatters.iterator();
    while (iterator.hasNext())
    {
      SimpleDateFormat format = iterator.next();
      if (format.toPattern().equals(formatString))
      {
        iterator.remove();
      }
    }

    userDefinedTimeStrings.remove(formatString);
  }
}

