package nloc;

import java.util.List;
import java.util.ArrayList;
import java.util.ListIterator;
import java.util.Arrays;

public class Nloc {

  public static final int MIN_TIMEDIFF = 6;
  private List<Channel> chanlist;
  private List<Droplet> dropletList;

  public Nloc (List<Channel> chanlist) {
    this.chanlist = chanlist;
    this.dropletList = new ArrayList<Droplet>();
  }

  public void addChannel(Channel chan) {
    chanlist.add(chan);
  }

  public Pump getPump() {
    Pump pump = null;
    for (Channel chan : chanlist) {
      if (chan instanceof Pump) pump = (Pump)chan;
    }
    return pump;
  }

  public boolean simulate() {
    boolean works = true;
    while (!allDropletsInSink()) {
      try {
        this.moveDroplets();
      } catch (CoalescedDropletException e) {
        works = false;
        System.out.println(e.getDroplet());
        break;
      }
    }
    return works;
  }

  public List<Droplet> getDropletList() {
    return dropletList;
  }

  public void moveDroplets() throws CoalescedDropletException {
    for (Droplet dr : dropletList) {
      dr.move();
    }
    for (Droplet dr: dropletList) {
      if (dr.coalesce()) throw new CoalescedDropletException(dr);
    }
  }

  //public Pump generateDropletSequence(List<String> modules) 
  //  throws BifurcationTooShortException {
  public List<BFTableEntry> generateDropletSequence(List<String> modules) {


    Pump pump = this.getPump();

    int timediff = MIN_TIMEDIFF + 1;

    List<Channel> modPath = this.getModulesByName(modules);
    List<List<Channel>> pathlist = this.getAllPaths();
    List<Channel> desiredPath = getDesiredPath(modPath, pathlist);

    List<BFTableEntry> startConfigruation = calcBFTable(desiredPath);


    //cumulatedDropletTimeDiffList = calcCumTDiffList(timediffList);
    //dropletList = generateDropletList(noOfDroplets);

    //pump = generateSequenceAsPump(dropletList, cumulatedDropletTimeDiffList);

    //for (int i = 0; i < noOfDroplets; i++){
    //  if (!pump.containsDroplets()) {
    //    dropletList.add(new Droplet(DropletType.PAYLOAD, 
    //        new Position(pump,1)));
    //  } else {
    //    dropletList.add(new Droplet(DropletType.HEADER, 
    //        new Position(pump,i + i * timediff + 1)));
    //  }
    //}
    return startConfigruation;
  }

  private List<BFTableEntry> calcBFTable(List<Channel> path) {
    // initialize table 
    List<BFTableEntry> bftable = new ArrayList<BFTableEntry>();
    
    ListIterator<Channel> iter = path.listIterator();
    while(iter.hasNext()) {
      Channel current = iter.next();
      if (current.isBifurcation()) {
        Channel following = iter.next();
        int prio = current.getChildrenList().indexOf(following) + 1;
        int bfMinSteps = current.getChildrenList().get(0).getHSteps();
        bftable.add(new BFTableEntry(prio, bfMinSteps));
        iter.previous();
      }
    } // end while table init

    /* calculate noOfDroplets and timediff range traversing the table from
     * "end" to "start"
     */ 
    for (int listPos = bftable.size(); listPos > 0; listPos--) {
      System.out.println("listpos: " + listPos + " Size: " + bftable.size());
      BFTableEntry cur = bftable.get(listPos - 1);
      BFTableEntry prev = null;
      if (listPos != bftable.size()) {
        prev = bftable.get(listPos);
      }

      if (cur.getBfPathPriority() == 2) {
        if (prev != null) {
          cur.setNoOfDroplets(prev.getNoOfDroplets() * 2);
          cur.setMaxTimediff(cur.getBfMinSteps());
          cur.setMinTimediff(prev.getMinTimediff());
        } else {
          cur.setNoOfDroplets(2);
          cur.setMaxTimediff(cur.getBfMinSteps());
        }
      } 
      if (cur.getBfPathPriority() == 1 && prev != null) {
        //TODO:
        //checkTableDown()
        //if violation
        //  mindist = previous.getMaxTimediff();
        //  maxdist = current.getMinTimediff();
        //  noOfDroplets = noOfDroplets * 2 -1;
        //if noviolation
        //  mindist = current.getMinTimediff();
        //  maxidst = previous.getMaxTimediff();
      } else {
        // throw some exception
      }
    }
    return bftable;


    //ListIterator<BFTableEntry> bftiter = bftable.listIterator(bftable.size());
    //int noOfDroplets = 1;
    //while(bftiter.hasPrevious()) {

    //  // get number of droplets from the following table entry 
    //  // (next in this case because of the reverse processing of the list)
    //  if (bftable.size() != bftable.indexOf(current) + 1) {
    //    BFTableEntry following = bftiter.next();
    //    noOfDroplets = following.getNoOfDroplets();
    //    bftiter.previous();
    //  }

    //  BFTableEntry current = bftiter.previous();
    //  int prio = current.getBfPathPriority();
    //  int minSteps = current.getBfMinSteps();
    //  int minTimediff = current.getMinTimediff();
    //  int maxTimediff = current.getMaxTimediff();


    //  if (prio == 2) {
    //    current.setNoOfDroplets(noOfDroplets * 2);
    //    current.setMaxTimediff(minSteps);
    //  } else if (prio == 1) {
    //    //TODO:
    //    //checkTableDown()
    //    //if violation
    //    //  mindist = previous.getMaxTimediff();
    //    //  maxdist = current.getMinTimediff();
    //    //  noOfDroplets = noOfDroplets * 2 -1;
    //    //if noviolation
    //    //  mindist = current.getMinTimediff();
    //    //  maxidst = previous.getMaxTimediff();
    //  } else {
    //    // throw some exception
    //  }
    //}
    //return bftable.get(0);
  }

  public boolean allDropletsInSink() {
    boolean allInSink = true;
    for (Droplet dr : dropletList) {
      allInSink &= dr.isInSink();
    }
    return allInSink;
  }

  //public int getNumberOfDroplets(List<Channel> desiredPath) 
  //  throws BifurcationTooShortException {

  //  int numOfDroplets = 1;
  //  ListIterator<Channel> iter = desiredPath.listIterator();
  //  while(iter.hasNext()) {
  //    Channel current = iter.next();
  //    if (current.isBifurcation()) {
  //      Channel following = iter.next();
  //      int neededMinLength = numOfDroplets * Nloc.MIN_TIMEDIFF + 
  //        numOfDroplets - 1;
  //      int prio = current.getChildrenList().indexOf(following) + 1;
  //      iter.previous();

  //      for (int i = 1; i < prio; i++) {

  //        int bifurcChildHSteps = current.getChildrenList().get(i).getHSteps();
  //        if (bifurcChildHSteps < neededMinLength) {
  //          throw new BifurcationTooShortException(current, neededMinLength);
  //        }
  //      } 
  //      numOfDroplets *= (current.getChildrenList().indexOf(following) + 1);
  //    }
  //  }
  //  return numOfDroplets;
  //}

  public List<Channel> getDesiredPath(List<Channel> modules,
      List<List<Channel>> pathlist) {
    List<Channel> found = new ArrayList<Channel>();
    for (List<Channel> path : pathlist) {
      if (path.containsAll(modules)) {
        found = path;
      }
    }
    return found;
  }

  private List<Channel> getModulesByName(List<String> names) {
    List<Channel> ret = new ArrayList<Channel>();
    for (String name : names) {
      for (Channel ch : chanlist) {
        if (ch instanceof Module && ch.getName().equals(name)) {
          ret.add(ch);
        } 
      }
    }
    return ret;
  }

  public List<List<Channel>> getAllPaths() {
    List<List<Channel>> pl = new ArrayList<List<Channel>>();
    List<Channel> path = new ArrayList<Channel>();
    getAllPathsRecursive(this.getPump(), path, pl);
    return pl;
  }

  private void getAllPathsRecursive(Channel chan, List<Channel> path, List<List<Channel>> pathlist) {
    path.add(chan);
    if (chan.getChildrenList().isEmpty()) { 
      pathlist.add(path);
    } else {
      for (Channel ch : chan.getChildrenList()) {
        getAllPathsRecursive(ch, new ArrayList<Channel>(path), pathlist);
      }
    }
  }

}