/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.jclouds.rackspace.cloudloadbalancers.v1.domain;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;

import org.jclouds.rackspace.cloudloadbalancers.v1.domain.internal.BaseNode;

import com.google.common.base.MoreObjects;
import com.google.common.base.Objects;
import com.google.common.base.MoreObjects.ToStringHelper;

/**
 * The nodes defined by the load balancer are responsible for servicing the requests received
 * through the load balancer's virtual IP. By default, the load balancer employs a basic health
 * check that ensures the node is listening on its defined port. The node is checked at the time of
 * addition and at regular intervals as defined by the load balancer health check configuration. If
 * a back-end node is not listening on its port or does not meet the conditions of the defined
 * active health check for the load balancer, then the load balancer will not forward connections
 * and its status will be listed as OFFLINE. Only nodes that are in an ONLINE status will receive
 * and be able to service traffic from the load balancer.
 * <p/>
 * All nodes have an associated status that indicates whether the node is ONLINE, OFFLINE, or
 * DRAINING. Only nodes that are in ONLINE status will receive and be able to service traffic from
 * the load balancer. The OFFLINE status represents a node that cannot accept or service traffic. A
 * node in DRAINING status represents a node that stops the traffic manager from sending any
 * additional new connections to the node, but honors established sessions. If the traffic manager
 * receives a request and session persistence requires that the node is used, the traffic manager
 * will use it. The status is determined by the passive or active health monitors.
 * <p/>
 * If the WEIGHTED_ROUND_ROBIN load balancer algorithm mode is selected, then the caller should
 * assign the relevant weights to the node as part of the weight attribute of the node element. When
 * the algorithm of the load balancer is changed to WEIGHTED_ROUND_ROBIN and the nodes do not
 * already have an assigned weight, the service will automatically set the weight to "1" for all
 * nodes.
 */
public class Node extends BaseNode<Node> {

   private int id;
   private Status status;
   private Metadata metadata = new Metadata();
   
   // for serialization only
   protected Node() {
   }

   public Node(String address, int port, Condition condition, Type type, Integer weight, int id, Status status, Metadata metadata) {
      super(address, port, condition, type, weight);
      checkArgument(id != -1, "id must be specified");
      this.id = id;
      this.status = checkNotNull(status, "status");
      this.metadata = metadata != null ? metadata : this.metadata;
   }

   public int getId() {
      return id;
   }

   public Status getStatus() {
      return status;
   }

   public Metadata getMetadata() {
      return metadata;
   }

   protected ToStringHelper string() {
      return MoreObjects.toStringHelper(this).omitNullValues()
            .add("id", id).add("address", address).add("port", port).add("condition", condition)
            .add("type", type).add("weight", weight).add("status", status).add("metadata", metadata);
   }
   
   @Override
   public String toString() {
      return string().toString();
   }

   @Override
   public int hashCode() {
      return Objects.hashCode(id);
   }

   @Override
   public boolean equals(Object obj) {
      if (this == obj) return true;
      if (obj == null || getClass() != obj.getClass()) return false;

      Node that = Node.class.cast(obj);
      return Objects.equal(this.id, that.id);
   }

   /**
    * The status is determined by the passive or active health monitors.
    */
   public static enum Status {
      /**
       * Only nodes that are in an ONLINE status will receive and be able to service traffic from
       * the load balancer.
       */
      ONLINE,

      /**
       * Represents a node that cannot accept or service traffic.
       */
      OFFLINE,

      /**
       * Represents a node that stops the traffic manager from sending any additional new
       * connections to the node, but honors established sessions.
       */
      DRAINING,

      UNRECOGNIZED;

      public static Status fromValue(String status) {
         try {
            return valueOf(checkNotNull(status, "status"));
         } catch (IllegalArgumentException e) {
            return UNRECOGNIZED;
         }
      }
   }

   public static class Builder extends BaseNode.Builder<Node> {
      private int id = -1;
      private Status status;
      private Metadata metadata;

      public Builder id(int id) {
         this.id = id;
         return this;
      }

      /**
       * @see Status
       */
      public Builder status(Status status) {
         this.status = status;
         return this;
      }

      public Builder metadata(Metadata metadata) {
         this.metadata = checkNotNull(metadata, "metadata");
         return this;
      }

      @Override
      public Node build() {
         return new Node(address, port, condition, type, weight, id, status, metadata);
      }

      /**
       * {@inheritDoc}
       */
      @Override
      public Builder address(String address) {
         return Builder.class.cast(super.address(address));
      }

      /**
       * {@inheritDoc}
       */
      @Override
      public Builder condition(Condition condition) {
         return Builder.class.cast(super.condition(condition));
      }

      /**
       * {@inheritDoc}
       */
      @Override
      public Builder type(Type type) {
         return Builder.class.cast(super.type(type));
      }      

      /**
       * {@inheritDoc}
       */
      @Override
      public Builder port(int port) {
         return Builder.class.cast(super.port(port));
      }

      /**
       * {@inheritDoc}
       */
      @Override
      public Builder weight(Integer weight) {
         return Builder.class.cast(super.weight(weight));
      }
      @Override
      public Builder from(Node in) {
         return Builder.class.cast(super.from(in)).id(in.getId()).status(in.getStatus()).metadata(in.getMetadata());
      }
   }

   @SuppressWarnings("unchecked")
   public static Builder builder() {
      return new Builder();
   }

   @Override
   public Builder toBuilder() {
      return new Builder().from(this);
   }
}
