package brn.sim.builder;

import jist.swans.Constants;
import jist.swans.Node;
import jist.swans.mac.AbstractMac;
import jist.swans.mac.MacAddress;
import jist.swans.mac.MacInfo;
import jist.swans.misc.Util;
import jist.swans.radio.RadioNoise;
import jist.swans.rate.*;

/**
 * @author kurth, ofriedri
 */
public abstract class RateBuilder extends Builder {

//	protected MacInfo macInfo = null;

//  public MacInfo getMacInfo() {
//    return macInfo;
//  }
//
//  public void setMacInfo(MacInfo macInfo) {
//    this.macInfo = macInfo;
//  }

	public static class Params extends Builder.Params {
		private static final long serialVersionUID = 1L;
		
    /**
     * The ID of the node's MAC this RCA will be used with (used to get that
     * MAC's <code>MacInfo</code> object).
     */
    int macId = 0;
	}

	public static class ConstantParams extends Params {
		private static final long serialVersionUID = 1L;

		/** constant bitrate to use */
		public int dataBitrate = Constants.BANDWIDTH_1Mbps;

		/** constant bitrate to use */
		public int controlBitrate = Constants.BANDWIDTH_1Mbps;

		public int getDataBitrate() {
			return dataBitrate;
		}

		public void setDataBitrate(int bitrate) {
			this.dataBitrate = bitrate;
		}

		public int getControlBitrate() {
			return controlBitrate;
		}

		public void setControlBitrate(int controlBitrate) {
			this.controlBitrate = controlBitrate;
		}
	}

	public static class AnnoParams extends Params {
		private static final long serialVersionUID = 1L;

		/** fallback rate selection algorithm when no annotations available */
		public Params rateSelection;

		public Params getRateSelection() {
			return rateSelection;
		}

		public void setRateSelection(Params rateSelection) {
			this.rateSelection = rateSelection;
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see brn.sim.builder.Builder.Params#clone()
		 */
		public Object clone() throws CloneNotSupportedException {
			AnnoParams ret = (AnnoParams) super.clone();
			if (null != rateSelection)
				ret.rateSelection = (Params) rateSelection.clone();
			return ret;
		}
	}

	public static class ArfParams extends Params {
		private static final long serialVersionUID = 1L;

//		public short macType;
	}

	public static class AarfParams extends ArfParams {
		private static final long serialVersionUID = 1L;
	}

  public static class AmrrParams extends Params {
    private static final long serialVersionUID = 1L;

//    public short macType;
  }

  public static class SampleParams extends Params {
		private static final long serialVersionUID = 1L;

//		public short macType;
	}

	/**
	 * Builder for constant rate selection.
	 * 
	 * @author kurth
	 */
	public static class Constant extends RateBuilder {

		public Class getParamClass() {
			return ConstantParams.class;
		}

		public Object build(Builder.Params params, Node node)
				throws BuilderException {
      // TODO id
      RadioNoise radio = (RadioNoise) node.getRadio(0);
      ConstantParams opts = (ConstantParams) params;

      // Validate given rates 
      int[] rates = radio.getRadioInfo().getBitratesSupported();
      if (0 > Util.isInArray(rates, opts.dataBitrate))
        throw new BuilderException("invalid bit-rate given ("
            + opts.dataBitrate + ")");
      if (0 > Util.isInArray(rates, opts.controlBitrate))
        throw new BuilderException("invalid bit-rate given ("
            + opts.controlBitrate + ")");
      
      return new ConstantRate(opts.dataBitrate, opts.controlBitrate);
		}

		public void hookUp(Builder.Params params, Node node, Object entity)
				throws BuilderException {
		}
	}

	/**
	 * Builder for rate selection through annotations.
	 * 
	 * @author kurth
	 */
	public static class Anno extends RateBuilder {

		public Class getParamClass() {
			return AnnoParams.class;
		}

		public Object build(Builder.Params params, Node node)
				throws BuilderException {
      AnnoParams opts = (AnnoParams) params;

      Builder builder = getProvider().getBuilder(opts.rateSelection);
      RateControlAlgorithmIF next = (RateControlAlgorithmIF) builder.build(
          opts.rateSelection, node);
      getProvider().addHookUp(builder, opts.rateSelection, node, next);
      
		  return new AnnoRate(next);
		}

		public void hookUp(Builder.Params params, Node node, Object entity)
				throws BuilderException {
		}
	}

	/**
	 * Builder for ARF (Auto Rate Fallback) rate selection.
	 * 
	 * @author ofriedri
	 */
	public static class Arf extends RateBuilder {

		public Class getParamClass() {
			return ArfParams.class;
		}

		public Object build(Builder.Params params, Node node)
				throws BuilderException {
      ArfParams opts = (ArfParams) params;
      AbstractMac mac = (AbstractMac) node.getMac(opts.macId);
      // Add the MacInfo of this node's default MAC to the global RCA memory
      AbstractRca.s_macRateInfo.setMacInfo(mac.getAddress(), mac.getMacInfo());
      addMacInfoForBroadcast(mac.getMacInfo());
      // Now create the RCA with the MacInfo object of the MAC layer
      return new AutoRateFallback(mac.getMacInfo());
		}

		public void hookUp(Builder.Params params, Node node, Object entity)
				throws BuilderException {
		}
	}

	/**
	 * Builder for AARF (Adaptive Auto Rate Fallback) rate selection.
	 * 
	 * @author ofriedri
	 */
	public static class Aarf extends RateBuilder {

		public Class getParamClass() {
			return AarfParams.class;
		}

		public Object build(Builder.Params params, Node node)
				throws BuilderException {
      AarfParams opts = (AarfParams) params;
      AbstractMac mac = (AbstractMac) node.getMac(opts.macId);
      // Add the MacInfo of this node's default MAC to the global RCA memory
      AbstractRca.s_macRateInfo.setMacInfo(mac.getAddress(), mac.getMacInfo());
      addMacInfoForBroadcast(mac.getMacInfo());
      // Now create the RCA with the MacInfo object of the MAC layer
      return new AdaptiveAutoRateFallback(mac.getMacInfo());
		}

		public void hookUp(Builder.Params params, Node node, Object entity)
				throws BuilderException {
		}
	}

  /**
   * Builder for AMRR (Adaptive Multi Rate Retry) rate selection.
   * 
   * @author ofriedri
   */
  public static class AmrrBuilder extends RateBuilder {

    public Class getParamClass() {
      return AmrrParams.class;
    }

    public Object build(Builder.Params params, Node node)
        throws BuilderException {
//      this.macInfo = node.getMac(0).getMacInfo(); // MacInfo.create(opts.macType);
//      AmrrHighLat amrr = new AmrrHighLat(this.macInfo);
//      return amrr;
      AmrrParams opts = (AmrrParams) params;
      AbstractMac mac = (AbstractMac) node.getMac(opts.macId);
      // Add the MacInfo of this node's default MAC to the global RCA memory
      AbstractRca.s_macRateInfo.setMacInfo(mac.getAddress(), mac.getMacInfo());
      addMacInfoForBroadcast(mac.getMacInfo());
      // Now create the RCA with the MacInfo object of the MAC layer
      return new AmrrHighLat(mac.getMacInfo());
    }

    public void hookUp(Builder.Params params, Node node, Object entity)
        throws BuilderException {
    }
  }

  /**
	 * Builder for SampleRate rate selection.
	 * 
	 * @author ofriedri
	 */
	public static class Sample extends RateBuilder {

		public Class getParamClass() {
			return SampleParams.class;
		}

		public Object build(Builder.Params params, Node node)
				throws BuilderException {
			SampleParams opts = (SampleParams) params;
//			this.macInfo = node.getMac(0).getMacInfo(); // MacInfo.create(opts.macType);
//			return new SampleRate(this.macInfo);
      AbstractMac mac = (AbstractMac) node.getMac(opts.macId);
      // Add the MacInfo of this node's default MAC to the global RCA memory
      AbstractRca.s_macRateInfo.setMacInfo(mac.getAddress(), mac.getMacInfo());
      addMacInfoForBroadcast(mac.getMacInfo());
      // Now create the RCA with the MacInfo object of the MAC layer
      return new SampleRate(mac.getMacInfo());
		}

		public void hookUp(Builder.Params params, Node node, Object entity)
				throws BuilderException {
		}
	}

  /** Switch to execute <code>addMacInfoForBroadcast()</code> only once. */
  private static boolean firstCall = true;

  /**
   * Add a MacInfo object for the broadcast MAC address (ANY) to the global RCA
   * memory
   */
  private static synchronized void addMacInfoForBroadcast(final MacInfo macInfo) {
    if (firstCall) {
      // Use a 802.11b MAC layer as default for broadcast messages
      // TODO: Use only the BSS basic rates as bit-rates for broadcast MacInfo.
      //       Leads to problems with LinkState proto bc it tries to send msgs
      //       at all 802.11g rates and notices if the bit-rates do not match.
//      MacInfo mi = MacInfo.create(macInfo.getMacType());
//      mi.setBitrateSet(mi.getBasicRateSet());
      AbstractRca.s_macRateInfo.setMacInfo(MacAddress.ANY, macInfo);
      firstCall = false;
    }
  }
}
