/*
 * JBoss, Home of Professional Open Source
 * Copyright 2005, JBoss Inc., and individual contributors as indicated
 * by the @authors tag. See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * This 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 (at your option) any later version.
 *
 * This software 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 software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.jboss.monitor.alarm;

import java.util.Map;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Collection;

import javax.management.Notification;
import javax.management.ObjectName;

/**
 * AlarmTable
 *
 * @author  <a href="mailto:dimitris@jboss.org">Dimitris Andreadis</a>
 *
 * @version $Revision: 1.1.2.3 $
**/
public class AlarmTable
{
   // Private/Protected Data ----------------------------------------

   // mediates the related MBean
   protected MBeanImplAccess mbeanImpl;
   
   /** the serverId to use when producing AlarmTableNotification */
   private String serverId;
   
   /** the active alarm table, contains AlarmTableNotification */
   private Map alarmMap; 
   
   // Constructors --------------------------------------------------
   
   /**
    * CTOR
   **/
   public AlarmTable(MBeanImplAccess mbeanImpl)
   {
      this.mbeanImpl = mbeanImpl;
      this.alarmMap = new HashMap();
   }
   
   // AlarmTable Implementation -------------------------------------

   /**
    * Sets the serverId
   **/   
   public void setServerId(String serverId)
   {
      this.serverId = serverId;
   }
   
   /**
    * Gets the serverId
   **/   
   public String getServerId()
   {
      return this.serverId;
   }

   /**
    * Update the AlarmTable based on the incoming Notification
   **/   
   public void update(Notification n)
   {
      if (n instanceof AlarmTableNotification) {
         // ignore - those notification are
         // meant to be produced only by me
      }
      else if (n instanceof AlarmNotification) {
         AlarmNotification an = (AlarmNotification)n;
         
         if (an.getAlarmState() == Alarm.STATE_NONE)
            updateNotificationStateless(n, an.getSeverity());
         else
            updateNotificationStatefull(an);
      }
      else
         updateNotificationStateless(n, Alarm.SEVERITY_UNKNOWN);
   }

   /**
    * Acknowledge an Alarm
    *
    * @return true if ack was succesful, false otherwise
    *         (not in table or acked already)
   **/
   public boolean acknowledge(String serverId, String source, String type,
                              String user, String system)
   {
      AlarmKey key = AlarmKey.createKey(serverId, source, type);
      
      return this.acknowledge(key, user, system);
   }
   
   /**
    * Acknowledge an Alarm
    *
    * @return true if ack was succesful, false otherwise
    *         (not in table or acked already)
   **/
   public boolean acknowledge(Object key, String user, String system)
   {
      AlarmTableNotification atn;
      
      synchronized (this) {
         AlarmTableNotification entry =
            (AlarmTableNotification)this.alarmMap.get(key);
         
         if (entry == null || entry.getAckState() == true)
            return false; // ack failed
            
         // ack the alarm
         entry.setAckParams(true, System.currentTimeMillis(), user, system);
         
         // prepare the AlarmTableNotification to send
         atn =  new AlarmTableNotification(entry);
         
         // this is a new notification
         atn.setSequenceNumber(this.mbeanImpl.getSequenceNumber());
         atn.setTimeStamp(System.currentTimeMillis());
         
         // if alarm Stateless or Statefull but Cleared, remove from table
         int alarmState = entry.getAlarmState();
         if (alarmState == Alarm.STATE_NONE || alarmState == Alarm.STATE_CLEARED)
            this.alarmMap.remove(key);         
      }
      // send the AlarmTableNotification
      this.mbeanImpl.emitNotification(atn);
      
      return true; // ok
   }

   /**
    * Uncknowledge an Alarm
    *
    * @return true if unack was succesful, false otherwise
    *         (not in table or unacked already)
   **/
   public boolean unacknowledge(String serverId, String source, String type,
                                String user, String system)
   {
      AlarmKey key = AlarmKey.createKey(serverId, source, type);
      
      return this.unacknowledge(key, user, system);
   }
   
   /**
    * Unacknowledge an Alarm
    *
    * @return true if unack was succesful, false otherwise
    *         (not in table or unacked already)
   **/
   public boolean unacknowledge(Object key, String user, String system)
   {
      AlarmTableNotification atn;
      
      synchronized (this) {
         AlarmTableNotification entry =
            (AlarmTableNotification)this.alarmMap.get(key);
         
         if (entry == null || entry.getAckState() == false)
            return false; // unack failed
            
         // unack the alarm
         entry.setAckParams(false, System.currentTimeMillis(), user, system);
         
         // prepare the AlarmTableNotification to send
         atn =  new AlarmTableNotification(entry);
         
         // this is a new notification
         atn.setSequenceNumber(this.mbeanImpl.getSequenceNumber());
         atn.setTimeStamp(System.currentTimeMillis());
      }
      // send the AlarmTableNotification
      this.mbeanImpl.emitNotification(atn);
      
      return true; // ok
   }
   
   /**
    * Gets a copy of the AlarmTable
   **/   
   public AlarmTableNotification[] getAlarmTable()
   {
      // this syncronized deep copy is quite expensive
      synchronized (this) {
         int size = this.alarmMap.size();
         
         AlarmTableNotification[] copy =
            new AlarmTableNotification[size];
            
         Collection values = this.alarmMap.values();
         
         Iterator i = values.iterator();
         while (i.hasNext()) {
            AlarmTableNotification atn =
               (AlarmTableNotification)i.next();
            
            copy[--size] = new AlarmTableNotification(atn);
         }
         return copy;
      }
   }
   
   // Private Methods -----------------------------------------------
   
   private void updateNotificationStatefull(AlarmNotification an)
   {
      int alarmState = an.getAlarmState();
      int severity = an.getSeverity();
      
      // create an AlarmTableNotification
      AlarmTableNotification atn =
         new AlarmTableNotification(
            AlarmTableNotification.ALARM_TABLE_UPDATE,
            this.mbeanImpl.getMBeanName(),
            this.mbeanImpl.getSequenceNumber(),
            System.currentTimeMillis(),
            null,
            alarmState,
            severity,
            this.serverId
         );
      // store a reference to the original notification
      atn.setUserData(an);
      
      Object key = atn.createKey();

      synchronized (this) {
         // need to check if acked already, in which case
         // we must copy the ack data to the new AlarmTableNotification
         // and remove the entry from the table         
         if (alarmState == Alarm.STATE_CLEARED) {
            AlarmTableNotification entry =
               (AlarmTableNotification)this.alarmMap.get(key);
            
            if (entry != null && entry.getAckState() == true) {
               this.alarmMap.remove(key);
               atn.setAckParams(true, entry.getAckTime(),
                                entry.getAckUser(), entry.getAckSystem());
            }
            else {
               // just add it
               this.alarmMap.put(key, atn);
            }
         }
         else {
            // just add it
            this.alarmMap.put(key, atn);
         }
      }
      // the only case to be acked is when it is not stored in table
      // in which case send the new AlarmTableNotification itself
      if (atn.getAckState() == true)
         this.mbeanImpl.emitNotification(atn);
      else // send a copy away
         this.mbeanImpl.emitNotification(new AlarmTableNotification(atn));      
   }
   
   private void updateNotificationStateless(Notification n, int severity)
   {
      // create an AlarmTableNotification
      AlarmTableNotification atn =
         new AlarmTableNotification(
            AlarmTableNotification.ALARM_TABLE_UPDATE,
            this.mbeanImpl.getMBeanName(),
            this.mbeanImpl.getSequenceNumber(),
            System.currentTimeMillis(),
            null,
            Alarm.STATE_NONE,
            severity,
            this.serverId
         );
      // store a reference to the original notification
      atn.setUserData(n);
      
      Object key = atn.createKey();
      
      // store the AlarmTableNotification - it may override an existing entry
      synchronized (this) {
         this.alarmMap.put(key, atn);
      }
      
      // send a copy away
      this.mbeanImpl.emitNotification(new AlarmTableNotification(atn));
   }
}
