/*
 * Licensed Materials - Property of Rogue Wave Software, Inc. 
 * © Copyright Rogue Wave Software, Inc. 2014, 2015 
 * © Copyright IBM Corp. 2009, 2014
 * © Copyright ILOG 1996, 2009
 * All Rights Reserved.
 *
 * Note to U.S. Government Users Restricted Rights:
 * The Software and Documentation were developed at private expense and
 * are "Commercial Items" as that term is defined at 48 CFR 2.101,
 * consisting of "Commercial Computer Software" and
 * "Commercial Computer Software Documentation", as such terms are
 * used in 48 CFR 12.212 or 48 CFR 227.7202-1 through 227.7202-4,
 * as applicable.
 */
package monitoring.generator;

import ilog.cpl.datasource.DataSourceEvent;
import ilog.cpl.datasource.DataSourceListener;
import ilog.cpl.datasource.DataSourceObjectEvent;
import ilog.cpl.datasource.IlpDataSource;
import ilog.cpl.datasource.IlpMutableDataSource;
import ilog.cpl.model.IlpObject;
import ilog.tgo.model.IltAlarm;
import ilog.tgo.model.IltNetworkElement;
import ilog.views.util.IlvResourceUtil;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;


/**
 * Abstact alarm generator that is to be used as a base for all alarm generator 
 * as it provides some common code. 
 */
abstract public class AbstractAlarmGenerator {

  /**
   * Contains the number of live threads that are actively generating alarms.  
   */
  private static int alarmGenerationLiveThreadCount = 0;

  /** Support for logging. */
  protected static Logger log = Logger.getLogger("generator");

  /** 
   * Random number generator used to randomly select property values from 
   * arrays. 
   */
  protected Random randomGenerator;

  /** The thread used to automatically generate new alarms. */
  protected AlarmGeneratorThread alarmGenerationThread;

  /** Data source to retrieve the managed objects that have alarms. */
  protected IlpMutableDataSource moDataSource;

  /** 
   * Cache with all business objects in the datasource to randomly choose 
   * an object when generating alarms.
   */
  protected Object[] moiCache;

 /**
   * List of alarms that have been generated and that can be changed. This list
   * is used as a basis for randomly selecting existing alarms to change.
   */
  protected List<Object> generatedAlarms;

  /**
   * The maximum number of alarms to be generated. After this number is reached 
   * no more alarms will be generated.
   */
  protected int maxNumberOfAlarms;
  
  /** Period (in ms) to wait between each alarm. */
  protected int waitPeriod;

  /**
   * List of possible alarm types. This array contains all the defined alarm
   * types and is used as a basis for randomly selecting an alarm type when
   * creating a new alarm event. 
   */
  protected static Object[] ALARM_TYPES;

  /**
   * List of possible alarm severities. This is an array containing all the
   * defined severities (except <code>CLEARED</code>). The array is used as a
   * source for selecting a random severity. 
   */
  protected static Object[] ALARM_SEVERITIES;

  /**
   * List of probable causes. This is an array of selected causes that is used
   * as a source for selecting a random cause. This is not an exhaustive list.
   */
  protected static Object[] PROBABLE_CAUSES;

  /**
   * List of possible alarm trends. This array contains a set of defined
   * alarm trends and is used as a basis for randomly selecting an alarm trend.
   * <code>TrendIndicationType.NO_CHANGE</code> is repeated several times to
   * weigh the probablility for it being picked. 
   */
  protected static Object[] ALARM_TRENDS;

  /**
   * The system identifier used by the generator. 
   */
  protected String systemId;
  
  /**
   * The user identifier used by the generator. 
   */
  protected String userId;

  /**
   * Creates an alarm generator.
   * 
   * @param period            Interval between the generation of two alarm 
   *                          events. 
   * @param maxNumberOfAlarms The maximum number of alarms to be generated.
   * 
   * @param moDataSource      The data source containing the managed objects
   *                          used as Managed Object Instances for the generated 
   *                          alarms.
   */
  public AbstractAlarmGenerator(int period, int maxNumberOfAlarms, IlpMutableDataSource moDataSource) {
    
    this.waitPeriod = period;
    this.moDataSource = moDataSource;
    this.maxNumberOfAlarms = maxNumberOfAlarms;
    
    // Cache to the objects in the datasource 
    this.moiCache = null;
    
    // Listen to datasource changes so that when they occur the cache
    // is cleared
    this.moDataSource.addDataSourceListener(new DataSourceListener() {
      public void batchEnded(DataSourceEvent e) {
      }
      public void batchStarted(DataSourceEvent e) {
      }
      public void objectsAdded(DataSourceEvent e) {
        clearMOICache();
      }
      public void objectsRemoved(DataSourceEvent e) {
        clearMOICache();
      }
      public void objectAttributeChanged(DataSourceObjectEvent e) {
      }
      public void objectStructureChanged(DataSourceObjectEvent e) {
      }
    });
    this.randomGenerator = new Random(new Date().getTime());
    this.generatedAlarms = new ArrayList<Object>(maxNumberOfAlarms);
  }
  
  /**
   * Should initialize the internal structures. This particular implementation 
   * does nothing. All subclasses must override this 
   * method to initialize the arrays of types for alarm trends, probable causes, 
   * alarm severities, and alarm types.
   * <p>
   * This method must be called just once, at initialization time.
   */
  public static void initialize() {    
  }

  /**
   * Should generate a single new alarm.
   */
  abstract protected void generateOneAlarm();

  /**
   * Should clears the given alarm.
   */
  abstract public void clearOneAlarm(Object alarm);

  /**
   * Should remove all alarms.
   */
  abstract public void removeAllAlarms();  

  /**
   * Return the system identifier used by the generator
   */
  public String getSystemId() {
    return systemId;
  }

  /**
   * Sets the system identifier used by the generator.
   */
  public void setSystemId(String systemId) {
    this.systemId = systemId;
  }

  /**
   * Returns the user identifier used by the generator.
   */
  public String getUserId() {
    return userId;
  }

  /**
   * Sets the user identifier used by the generator.
   */
  public void setUserId(String userId) {
    this.userId = userId;
  }
  
  /**
   * Sets the pause period between generated alarms. This is the amount of time
   * the generator will pause before sending the new alarm event.
   * 
   * @param period  Number of milliseconds to pause before sending a new alarm
   *                event.
   */
  public void setPeriod(int period) {
    this.waitPeriod = period;
  }

  /**
   * Returns the pause period between generated alarms.
   * 
   * @return  The current waiting period before sending a new alarm, given as 
   *          the number of milliseconds.
   */
  public int getPeriod() {
    return this.waitPeriod;
  }

  /**
   * Returns the maximum number of alarms to be generated.
   * 
   * @return  The maximum number of alarms to be generated.
   */
  public int getMaxNumberOfAlarms() {
    return maxNumberOfAlarms;
  }

  /**
   * Sets the maximum number of alarms to be generated.
   * 
   * @param maxNumberOfAlarms the maximum number of alarms to be generated.
   */
  public void setMaxNumberOfAlarms(int maxNumberOfAlarms) {
    this.maxNumberOfAlarms = maxNumberOfAlarms;
  }

  /**
   * Returns the <code>IlpDataSource</code> used by the generator to select 
   * managed object instances.
   * 
   * @return  The data source containing the managed object instances.
   */
  public IlpDataSource getMODataSource() {
    return this.moDataSource;
  }

  /**
   * Returns the number of generated alarms.
   * 
   * @return the number of generated alarms.
   */
  public int getNumberOfGeneratedAlarms() {
    return generatedAlarms.size();
  }
  /**
   * Create a new thread that constantly generates new alarm events waiting for
   * the specified wait period between publishing each new alarm.
   */
  public void startAlarmGeneration() {

    if (getPeriod() <= 0) {
      String msg=IlvResourceUtil.getServerLocaleString(AbstractAlarmGenerator.class,"NotStartAlarmGeneration");
      log.log(Level.WARNING, 
          msg,
          new Integer(getPeriod()));
      return;
    }

      alarmGenerationThread = new AlarmGeneratorThread();
      String msg=IlvResourceUtil.getServerLocaleString(AbstractAlarmGenerator.class,"StartingAlarmGeneration");
      log.log(Level.INFO, msg,new Integer(getPeriod()));

    alarmGenerationThread.start();    
  }

  /**
   * Stops the alarm generation thread.
   */
  public void stopAlarmGeneration() {
    if (alarmGenerationThread != null) {
      log.info("Requesting alarm generation to stop");
      alarmGenerationThread.requestStop();
    }
  }

  /**
   * Adds an alarm event to the list of generated alarms.
   * 
   * @param event  The alarm event to add.
   */
  public void addAlarm(IltAlarm event) {
    synchronized (generatedAlarms) {
      generatedAlarms.add(event);
    }
  }

  /**
   * Removes an alarm from the list of generated alarms.
   * 
   * @param event The alarm to remove.
   */
  public void removeAlarm(IltAlarm alarm) {
    synchronized (generatedAlarms) {
      generatedAlarms.remove(alarm);
    }
  }

  /**
   * Returns the Managed Object Class of a given MOI.
   * 
   * @param moi  A Managed Object Instance.
   * @return  The Managed Object Class of the Managed Object Instance.
   */
  protected String getMOC(IlpObject moi) {
    if (moi instanceof IltNetworkElement) {
      IltNetworkElement ne = (IltNetworkElement) moi;
      String moc = "ManagedElement/" + ne.getType().getName();
      return moc;
    } else {
      return moi.getIlpClass().getName();
    }
  }

  /**
   * Returns a randomly generated Managed Object Instance.
   * 
   * @return  A randomly selected Managed Object Instance.
   */
  protected synchronized IlpObject generateRandomMOI() {
    if (this.moiCache == null)
      this.moiCache = moDataSource.getObjects().toArray();

    //No IltAlarm instances should be returned
    IlpObject moi = null;
    do {
      moi = (IlpObject) moiCache[randomGenerator.nextInt(moiCache.length)];
    } while (moi instanceof IltAlarm);

    return moi;
  }

  /**
   * Generates a random severity value.
   * 
   * @return  A randomly generated severity value.
   */
  protected Object generateRandomSeverity() {
    return ALARM_SEVERITIES[randomGenerator
                            .nextInt(ALARM_SEVERITIES.length)];
  }

  /**
   * Generates a random probable cause value.
   * 
   * @return  A randomly generated probable cause value.
   */
  protected Object generateRandomProbableCause() {
    return PROBABLE_CAUSES[randomGenerator.nextInt(PROBABLE_CAUSES.length)];
  }

  /**
   * Generates a random alarm type value.
   * 
   * @return   randomly generated alarm type.
   */
  protected Object generateRandomAlarmType() {
    return ALARM_TYPES[randomGenerator.nextInt(ALARM_TYPES.length)];
  }

  /**
   * Generates a random alarm type value.
   * 
   * @return  A randomly generated trend value.
   */
  protected Object generateRandomTrend() {
    return ALARM_TRENDS[randomGenerator.nextInt(ALARM_TRENDS.length)];
  }

  /**
   * Clear the cache with all the managed object instances.
   * <p>This method is used when objects are added or removed from the
   * datasource.
   */
  protected synchronized void clearMOICache() {
    this.moiCache = null;
  }
  /**
   * Thread class used to run the alarm generator. The thread continually
   * generates new alarm events using the current alarm generator settings until
   * the thread is stopped. The thread can (and should) be stopped gently by
   * calling <code>requestStop</code>.
   * 
   * <p>Alarms are generated using the given interval as the number of 
   * milliseconds to pause between each new alarm being generated.
   */
  protected class AlarmGeneratorThread extends Thread {
    // Track request to stop the thread (gently)
    private boolean stopRequested = false;
    //Check if the maximum number of alarm has been reached
    private boolean maximumNumberReached=false;

    /**
     * Constructor.
     */
    AlarmGeneratorThread() {
      super("Alarm Generation " + alarmGenerationLiveThreadCount++);      
    }

    /**
     * Run the thread.
     */
    public void run() {
      while (!isStopRequested()) {
        
        try {
          //Enforce the max number of alarms 
          if(generatedAlarms.size() < maxNumberOfAlarms) {
            generateOneAlarm();
            maximumNumberReached = false;
          } else {
            if(maximumNumberReached==false){
              maximumNumberReached=true;
              log.info("Maximum number of alarms has been reached '" + maxNumberOfAlarms + "' no more alarms being generated");                           
            }
          }
          clearOneAlarm(null);
          Thread.sleep(waitPeriod);
        } catch (Exception e) {
          e.printStackTrace();
          requestStop();
        }
      }
      log.info("Alarm generation stopped...");
      alarmGenerationLiveThreadCount--;
      
      if(alarmGenerationThread == this)
      alarmGenerationThread = null;
    }

    /**
     * Request a stop of the alarm generation.
     */
    public synchronized void requestStop() {
      stopRequested = true;
    }

    /**
     * Synchronized access to the stopRequested variable.
     * @return A boolean indicating if a request has been issued to stop the
     *         thread.
     */
    synchronized boolean isStopRequested() {
      return stopRequested;
    }
  }
}