/*
 * Copyright (c) 1998-2012 Caucho Technology -- all rights reserved
 *
 * This file is part of Resin(R) Open Source
 *
 * Each copy or derived work must preserve the copyright notice and this
 * notice unmodified.
 *
 * Resin Open Source is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * Resin Open Source 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, or any warranty
 * of NON-INFRINGEMENT.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Resin Open Source; if not, write to the
 *
 *   Free Software Foundation, Inc.
 *   59 Temple Place, Suite 330
 *   Boston, MA 02111-1307  USA
 *
 * @author Scott Ferguson
 */

package com.caucho.cloud.topology;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.caucho.management.server.ClusterMXBean;
import com.caucho.util.L10N;

/**
 * Defines a set of clustered servers.
 */
public class CloudCluster
{
  private static final L10N L = new L10N(CloudCluster.class);
  private static final Logger log = Logger.getLogger(CloudCluster.class.getName());
  
  private static final int POD_INDEX_MAX = 64 * 64;

  private final CloudSystem _domain;
  private final String _id;

  private CloudPod []_podList = new CloudPod[0];
  
  private CloudClusterAdmin _admin;
  
  private final CopyOnWriteArrayList<CloudPodListener> _listeners
    = new CopyOnWriteArrayList<CloudPodListener>();
  
  private final ConcurrentHashMap<Class<?>,Object> _dataMap
    = new ConcurrentHashMap<Class<?>,Object>();

  CloudCluster(CloudSystem domain, String id)
  {
    _domain = domain;
    _id = id;
  }

  /**
   * Gets the cluster id.
   */
  public String getId()
  {
    return _id;
  }
  
  /**
   * Returns the owning domain.
   */
  public CloudSystem getSystem()
  {
    return _domain;
  }

  /**
   * Returns the list of pods for the cluster
   */
  public CloudPod []getPodList()
  {
    return _podList;
  }
  
  public ClusterMXBean getAdmin()
  {
    return _admin;
  }
  
  //
  // lifecycle
  //
  
  void init()
  {
  }
  
  void start()
  {
    _admin = new CloudClusterAdmin(this);
    _admin.register();
  }
  
  void destroy()
  {
    if (_admin != null)
      _admin.unregister();
  }

  /**
   * Finds the first server with the given server-id.
   */
  public CloudServer findServer(String id)
  {
    for (CloudPod pod : _podList) {
      CloudServer server = pod.findServer(id);

      if (server != null)
        return server;
    }

    return null;
  }

  /**
   * Finds the first server with the given server-id.
   */
  public CloudServer findServerByUniqueClusterId(String id)
  {
    for (CloudPod pod : _podList) {
      CloudServer server = pod.findServerByUniqueClusterId(id);

      if (server != null)
        return server;
    }

    return null;
  }

  /**
   * Finds the first server with the given server-id.
   */
  public CloudServer findServer(int podIndex,
                                int index)
  {
    CloudPod pod = findPod(podIndex);
      
    if (pod != null)
      return pod.findServer(index);
    else
      return null;
  }

  /**
   * Returns the pod with the given index.
   */
  public CloudPod findPod(int podIndex)
  {
    CloudPod []podList = _podList;
    
    if (podIndex < podList.length)
      return podList[podIndex];
    else
      return null;
  }

  /**
   * Returns the pod with the given index.
   */
  public CloudPod findPod(String id)
  {
    for (CloudPod pod : _podList) {
      if (pod != null && id.equals(pod.getId()))
        return pod;
    }
    
    return null;
  }
  
  /**
   * Creates a new pod
   */
  public CloudPod createPod()
  {
    return createPod(-1);
  }
  
  /**
   * Creates a new pod
   */
  public CloudPod createPod(int index)
  {
    CloudPod pod = null;
    
    synchronized (this) {
      if (POD_INDEX_MAX <= index)
        throw new IllegalArgumentException(L.l("'{0}' is an invalid pod index because it's greater than the max value.",
                                               index));

      if (index > 0 && index < _podList.length && _podList[index] != null) {
        throw new IllegalStateException(L.l("'{0}' is an invalid pod index because that pod already exists {1}",
                                            index, _podList[index]));
      }
      
      for (int i = 0; i < _podList.length; i++) {
        CloudPod oldPod = _podList[i];
        
        if (oldPod == null && index < 0) {
          index = i;
        }
      }
      
      if (index < 0)
        index = _podList.length;
      
      pod = new CloudPod(this, null, index);
      
      if (_podList.length <= index) {
        CloudPod []podList = new CloudPod[index + 1];
        System.arraycopy(_podList, 0, podList, 0, _podList.length);
        _podList = podList;
      }
      
      _podList[index] = pod;
    }
    
    for (CloudPodListener listener : _listeners) {
      listener.onPodAdd(pod);
    }
    
    if (log.isLoggable(Level.FINEST))
      log.finest(this + " added " + pod);
    
    return pod;
  }
  
  /**
   * Creates a new cluster.
   */
  public boolean removePod(int index)
  {
    if (index < 0 || _podList.length <= index)
      return false;
    
    CloudPod pod = null;
    
    synchronized (this) {
      pod = _podList[index];
      
      if (pod == null)
        return false;
      
      for (CloudServer server : pod.getServerList()) {
        if (server != null) {
          throw new IllegalStateException(L.l("{0} may not be removed because it has a non-empty server list",
                                              pod));
        }
      }
      
      // XXX: possibly always make a copy
      _podList[index] = null;
      
      int tail = _podList.length - 1;
      for (; tail >= 0 && _podList[tail] == null; tail--) {
      }

      if (tail + 1 < _podList.length) {
        CloudPod []podArray = new CloudPod[tail + 1];
        
        System.arraycopy(_podList, 0, podArray, 0, tail + 1);
      
        _podList = podArray;
      }
    }
    
    for (CloudPodListener listener : _listeners) {
      listener.onPodRemove(pod);
    }
    
    return true;
  }

  /**
   * Finds the first server with the given server-id.
   */
  public CloudServer findServer(String address, int port)
  {
    for (CloudPod pod : _podList) {
      CloudServer server = pod.findServer(address, port);

      if (server != null)
        return server;
    }

    return null;
  }
  
  //
  // data
  //
  
  public void putData(Object value)
  {
    _dataMap.put(value.getClass(), value);
  }
  
  @SuppressWarnings("unchecked")
  public <T> T putDataIfAbsent(T value)
  {
    return (T) _dataMap.putIfAbsent(value.getClass(), value);
  }
  
  @SuppressWarnings("unchecked")
  public <T> T getData(Class<T> cl)
  {
    return (T) _dataMap.get(cl);
  }

  //
  // listeners
  //
  
  /**
   * Adds a listener to detect pod add and remove.
   */
  public void addPodListener(CloudPodListener listener)
  {
    if (! _listeners.contains(listener))
      _listeners.add(listener);
    
    for (CloudPod pod : getPodList()) {
      listener.onPodAdd(pod);
    }
  }
  
  /**
   * Removes a listener to detect pod add and remove.
   */
  public void removePodListener(CloudPodListener listener)
  {
    _listeners.remove(listener);
  }

  @Override
  public String toString()
  {
    return getClass().getSimpleName() + "[" + _id + "]";
  }
}
