package brn.sim.builder;

import java.util.List;

import jist.swans.Constants;
import jist.swans.Node;
import jist.swans.mac.MacAddress;
import jist.swans.mac.MacInfo;
import jist.swans.net.AbstractNet;
import jist.swans.net.NetAddress;
import brn.swans.route.metric.ArpTable;
import brn.swans.route.metric.ArpTableInterface;
import brn.swans.route.metric.ETTMetric;
import brn.swans.route.metric.LinkTable;
import brn.swans.route.metric.RouteEtxEttMetric;
import brn.swans.route.metric.RouteHopCountMetric;

public abstract class MetricBuilder extends Builder {

  public static class EtxParams extends Builder.Params {
    private static final long serialVersionUID = 1L;

    /** period of a full probing cycle (msec) */
    public int period = 500;
    /** averaging interval for link qualities (msec) */
    public int tau = 10000;
    /** array of rate x packetsize for probes */
    public int[] probes = new int[] {
        Constants.BANDWIDTH_1Mbps, 1000,
        Constants.BANDWIDTH_2Mbps, 1000,
        Constants.BANDWIDTH_5_5Mbps, 1000,
        Constants.BANDWIDTH_11Mbps, 1000,
    };
    /** Indices of the interfaces to probe on. */
    public int[] netInterfaces = new int[] {Constants.NET_INTERFACE_DEFAULT};
    /** array of available channels */
    public int numberOfChannels = Constants.CHANNEL_NUMBER_DEFAULT;
    /** whether to use a global link table */
    public boolean globalLinkTable = false;
    /** radio frequency */
    public long frequency = Constants.FREQUENCY_DEFAULT; // 2.4 GHz
    /** route cache timeout, be aware that it should be less than candidateCacheTimeout */
    public long routeCacheTimeout = 500 * Constants.MILLI_SECOND;
    /** whether to use fast link table implementation */
    public boolean useFastLinkTable = false;
    /**
     * bit rate to calculate the ETT metric for, or -1 for the usage of the
     * best bit rate.
     */
    public int bitRate = -1;
    /** whether to automatically start the beaconing, otherwise use start() */
    public boolean autoStart = true;
    /** list of (List of NetAddress), fixed routes */
    public List fixedRoutes = null;

    public List getFixedRoutes() {
      return fixedRoutes;
    }
    public void setFixedRoutes(List fixedRoutes) {
      this.fixedRoutes = fixedRoutes;
    }
    public int getBitRate() {
      return bitRate;
    }
    public void setBitRate(int bitRate) {
      this.bitRate = bitRate;
    }
    public long getFrequency() {
      return frequency;
    }
    public void setFrequency(long frequency) {
      this.frequency = frequency;
    }
    public boolean isGlobalLinkTable() {
      return globalLinkTable;
    }
    public void setGlobalLinkTable(boolean globalLinkTable) {
      this.globalLinkTable = globalLinkTable;
    }
    public int getPeriod() {
      return period;
    }
    public void setPeriod(int period) {
      this.period = period;
    }
    public int[] getProbes() {
      return probes;
    }
    public void setProbes(int[] probes) {
      this.probes = probes;
    }
    public int getNumberOfChannels() {
      return numberOfChannels;
    }

    public void setNumberOfChannels(int numberOfChannels) {
      this.numberOfChannels = numberOfChannels;
    }
    public int getTau() {
      return tau;
    }
    public void setTau(int tau) {
      this.tau = tau;
    }
    public long getRouteCacheTimeout() {
      return routeCacheTimeout;
    }
    public void setRouteCacheTimeout(long routeCacheTimeout) {
      this.routeCacheTimeout = routeCacheTimeout;
    }
    public boolean isUseFastLinkTable() {
      return useFastLinkTable;
    }
    public void setUseFastLinkTable(boolean useFastLinkTable) {
      this.useFastLinkTable = useFastLinkTable;
    }
    /* (non-Javadoc)
     * @see brn.sim.builder.Builder.Params#clone()
     */
    public Object clone() throws CloneNotSupportedException {
      EtxParams ret = (EtxParams) super.clone();
      if (null != probes) ret.probes = (int[]) probes.clone();
      if (null != netInterfaces) ret.netInterfaces = (int[]) netInterfaces.clone();
      return ret;
    }
    public boolean isAutoStart() {
      return autoStart;
    }
    public void setAutoStart(boolean autoStart) {
      this.autoStart = autoStart;
    }
    public int[] getNetInterfaces() {
      return netInterfaces;
    }
    public void setNetInterfaces(int[] netInterfaces) {
      this.netInterfaces = netInterfaces;
    }
  }

  public static class EttParams extends EtxParams {
    private static final long serialVersionUID = 1L;
  }

  public static class HopCountParams extends Builder.Params {
    private static final long serialVersionUID = 1L;
  }

  public static class Etx extends MetricBuilder {
    protected LinkTable globLinkTable = null;

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

    public Object build(Builder.Params opts, Node node) throws BuilderException {
      EtxParams params = (EtxParams) opts;
      RouteEtxEttMetric metric;
      int nodeId = node.getNodeId();
      AbstractNet net = node.getNet();
      NetAddress addr = new NetAddress(node.getNodeId());

      long etxPeriod = params.period * Constants.MILLI_SECOND;
      long etxTau = params.tau * Constants.MILLI_SECOND;

      ArpTableInterface arpTable = new ArpTable(addr, new MacAddress(nodeId));

//      int linkTableId = (params.globalLinkTable ? -1 : nodeId);
      if (params.globalLinkTable && globLinkTable != null)
        metric = RouteEtxEttMetric.buildEtxMetric(net.getNode(), addr,
            new MacAddress(nodeId), etxPeriod, etxTau, params.probes,
            params.numberOfChannels, null, params.frequency, globLinkTable, arpTable,
            params.bitRate, params.routeCacheTimeout);
      else
        metric = RouteEtxEttMetric.buildEtxMetric(net.getNode(), addr,
            new MacAddress(nodeId),  etxPeriod, etxTau, params.probes,
            params.numberOfChannels, params.frequency, arpTable,
            params.useFastLinkTable, params.bitRate, params.routeCacheTimeout);

      metric.setNetInterfaces(params.netInterfaces);
      
      for (int i=0; params.fixedRoutes != null && i < params.fixedRoutes.size(); i++)
        metric.addFixedRoute((List) params.fixedRoutes.get(i), 50000000); // TODO minMetric
      
      globLinkTable = metric.getLinkTable();
      return metric;
    }

    public void hookUp(Builder.Params opts, Node node, Object entity)
        throws BuilderException {
      EtxParams params = (EtxParams) opts;
      RouteEtxEttMetric metric = (RouteEtxEttMetric) entity;
      AbstractNet net = (AbstractNet) node.getNet();

//      Phy802_11 phy = node.getMac(0).getPhy();
//      MacInfo macInfo = node.getMac(0).getMacInfo();

      // TODO refactor LinkProbeConversionHandler to NetBuilder
      metric.setNetEntity(net.getProxy());
      net.setProtocolHandler(Constants.NET_PROTOCOL_LINK_PROBE,
          new RouteEtxEttMetric.LinkProbeConversionHandler(metric.getProxy()));

      if (params.autoStart)
        metric.getProxy().start();
    }
  }

  public static class Ett extends Etx {

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

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

      RouteEtxEttMetric metric = (RouteEtxEttMetric) super.build(opts, node);
      ETTMetric ettMetric = new ETTMetric(metric.getLocalAddr(),
          metric.getLinkTable(), params.bitRate);
      metric.getLinkStat().setMetric(ettMetric);

      return metric;
    }

    public void hookUp(Builder.Params opts, Node node, Object entity)
        throws BuilderException {
//      EttParams params = (EttParams) opts;
      RouteEtxEttMetric metric = (RouteEtxEttMetric) entity;

//      Phy802_11 phy = node.getMac(0).getPhy();
      MacInfo macInfo = node.getMac(0).getMacInfo();

      ETTMetric ettMetric = (ETTMetric) metric.getLinkStat().getMetric();
      ettMetric.setMacInfo(macInfo);
      
      super.hookUp(opts, node, entity);
    }
  }

  public static class HopCount extends MetricBuilder {

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

    public Object build(Params params, Node node) throws BuilderException {
      int nodeId = node.getNodeId();
      NetAddress addr = new NetAddress(node.getNodeId());

      ArpTableInterface arpTable = new ArpTable(addr, new MacAddress(nodeId));

      return new RouteHopCountMetric(nodeId, addr, new MacAddress(nodeId), arpTable);
    }

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