/*
 *This file is part of AdmissibleFPG.
 *
 * Copyright (C) 2014 William Pettersson <william@ewpettersson.se>
 *
 *    AdmissibleFPG 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 3 of the License, or
 *    (at your option) any later version.
 *
 *    AdmissibleFPG 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.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with AdmissibleFPG.  If not, see <http://www.gnu.org/licenses/>.
 */
package se.ewpettersson.admissiblefpg;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import se.ewpettersson.admissiblefpg.fpg.Arc;
import se.ewpettersson.admissiblefpg.fpg.Vertex;

public class ConfigIterator implements Iterator<Config> {

	
	public class SymClass {
		int sym;
		int f1;
		int f2;
		public SymClass() {
			sym=0;
			f1=0;
			f2=0;
		}
		
		public String toString() {
			return "[" + sym + ":" + f1 + ":" + f2 + "]";
		}
	}
	
	Vertex v;
	Config n;
	
	List<ConfigIterator> children;
	List<Config> configs;
	List<Config> configsHere;
	List<Arc> arcsAdded;
	Config[] stackConfigs;
//	int[] syms;
	List<SymClass> syms;
	boolean nothing;
	boolean configsFound;
	int configsIndex;
	int changed;
	int count;
	boolean onlyChild;
	boolean foundNext;
	boolean first;
	
	public ConfigIterator(Vertex v, boolean onlyChild) {
		this.v=v;
		this.onlyChild = onlyChild;
		n=null;
		children = new ArrayList<ConfigIterator>(v.getNumChildren());
		configs = new ArrayList<Config>(v.getNumChildren());
		configsHere = new ArrayList<Config>();
		arcsAdded = new ArrayList<Arc>(v.getArcsAdded());
		stackConfigs = new Config[v.getArcsAdded().size()+1];
		nothing=false;
		configsFound=false;
		configsIndex=0;
		// If this is not an only child, we will need to re-use configs so we will store them
		// count=-1 means we have not yet counted them. We count them only when we need to.
		count=-1;
		// However if this is the only child, we won't be storing configs at all
		// So instead keep a live count which starts at 0
		if (onlyChild) {
			count = 0;
		}
		for(Vertex child: v.children()) {
			boolean only = ( v.getNumChildren()==1);
			ConfigIterator it = new ConfigIterator(child, only);
			children.add(it);
			if(!it.hasNext()) { // One of the children has no possible configurations, no point proceeding?
				nothing=true;
			} else {
				configs.add(it.next());
			}
		}
		
		syms= new ArrayList<SymClass>();
		for(int i=0; i < arcsAdded.size(); i++) {
			syms.add(new SymClass());
		}
		changed=0;
		resetSyms();
		if (nothing) {
			n=null;
			foundNext = true;
		} else {
			first=true;
			foundNext = false;
		}
	}

	@Override
	public boolean hasNext() {
		if(configsFound) {
			return(configsIndex < configsHere.size());
		}
		if (!foundNext) {
			n = recurse();
			if (!onlyChild) {
				configsHere.add(n);
			} else {
				count+=1;
			}
			foundNext = true;
		}
		return (n!=null);
	}

	private void resetSyms() {
		for(SymClass sim: syms) {
			sim.sym=0;
			sim.f1=0;
			sim.f2=0;
		}
	}
	
	private boolean nextSym(int i) {
		if (i == arcsAdded.size()) {
			return false;
		}
		SymClass sim = syms.get(i);
		if( ! nextSym(i+1)) {
			sim.sym += 1;
			changed = i;
		}
		if (sim.sym == 6) {
			sim.sym = 0;
			sim.f1 += 1;
			if (sim.f1 == 4) {
				sim.f1 = 0;
				sim.f2 += 1;
				if (sim.f2 == 4) {
					sim.f2 = 0;
					syms.set(i,sim);
					return false;
				}
			}
		}
		syms.set(i, sim);
		return true;
	}
	
	private Config recurse() {
		Config c = null;
		// First run through we don't increment the symmetries or find the next config.
		if (first) {
			first=false;
			
			stackConfigs[0] = makeConfig();
			// Just try to make the arcs
			c = addArc(0);
			// If we're successful, return this, otherwise we continue on with
			// the rest of this method, which moves on to the next symmetry
			if (c != null) {
				return c;
			}
		}
		// Look for a new config to return
		while(c==null) {
			//System.out.println(syms.toString());
			// Try to move to the next set of symmetries. If we're totally reset, 
			// then try to move on to the next config. If we're also out of those,
			// we're done so return null.
			if (! nextSym(0)) {
				if(! nextConfig(0)) {
					return null;
				}
				stackConfigs[0] = makeConfig();
			}

			// Now add the arcs. If this works, the while loop breaks and we return.
			// Otherwise the loop continues and we move on to the next symmetry/config.
			c = addArc(changed);
		}
		return c;
	}
	
	private Config makeConfig() {
		Config c = new Config();
		for(Config o: configs) {
			if ( o!= null) 
				c.mergeWith(o);
		}
		for(int t:v.getToAdd()) {
			c.addTetrahedra(t);
		}
		return c;
	}
	

	private boolean nextConfig(int i) {
		if (i == children.size()) {
			return false;
		}
		ConfigIterator it = children.get(i);

		boolean next = nextConfig(i+1);
		if (!next) {
			if (it.hasNext()) {
				configs.set(i, it.next());
				return true;
			} else {
				it.reset();
				Config newConfig = it.next();
				//ConfigIterator newIt = new ConfigIterator(v.children().get(i));
				//children.set(i, newIt);
				//Config newConfig = newIt.next();
				if(newConfig == null) {
					nothing=true;
					return false;
				}
				configs.set(i, newConfig);
				return false;
			}
		} 
		return true;
	}
	
	private void reset() {
		configsFound = true;
		configsIndex = 0;
		count = getCount();
		if(children != null) { 
			children.clear();
			children = null;
		}
		if(configs != null) {
			configs.clear();
			configs = null;
		}
	}

	@Override
	public Config next() {
		if(configsFound) {
			if(configsHere.size()<=configsIndex) {
				return null;
			}
			return configsHere.get(configsIndex++);
		}
		if(nothing) {
			return null;
		}
		if(!foundNext) {
			n = recurse();
			// If not only child, store config, else add one to live count
			if (!onlyChild) {
				configsHere.add(n);
			} else {
				count+=1;
			}
		}
		foundNext = false;
		return n;
	}

	@Override
	public void remove() {
		// TODO Auto-generated method stub
		
	}

	private Config addArc(int i) {
		if (arcsAdded.size() == i) {
			// If we've already seen this config, don't find it again.
			if (configsHere.contains(stackConfigs[i]))
				return null;
			return stackConfigs[i];
		}
		Arc a = arcsAdded.get(i);
		SymClass sim = syms.get(i);
		Config copy = new Config(stackConfigs[i]);
		if (!copy.onBoundary(a.t1,sim.f1) || 
			!copy.onBoundary(a.t2,sim.f2)
			// Don't glue faces to themselves.
			 || (a.t1 == a.t2 && sim.f1 == sim.f2)
			// If attempting to glue face 0 to something, only attempt identity mapping
			 || (sim.f1 == 0 && sim.sym > 0)
			 || (sim.f2 == 0 && sim.sym > 0)
			// If face 0 is not glued, don't try other faces
			|| (sim.f1 > 0 && copy.onBoundary(a.t1, 0))
			|| (sim.f2 > 1 && a.t1 != a.t2 && copy.onBoundary(a.t2, 0))
				) {
//			if (sim.f2 > 1 && a.t1 != a.t2 && copy.onBoundary(a.t2, 0)) {
//				System.out.println("Skipping on t1,t2="+a.t1+","+a.t2 + " and sim.f2="+sim.f2);
//				System.out.println(copy.ec.toString());
//			}
			for(SymClass sym: syms.subList(i+1, syms.size())) {
				sym.sym = 5;
				sym.f1 = 3;
				sym.f2 = 3;
			}
			return null;
		}


		Gluing g = new Gluing(sim.sym,a.t1,sim.f1,a.t2,sim.f2);

//		System.out.println("Gluing "+g);
		if (copy.addGluing(g) ) {
//			copy.addDescription(g.toString());
//			System.out.println(copy.getDescriptions());

			stackConfigs[i+1] = copy;
			Config good = addArc(i+1);
			if (good != null) {
				return good;
			}
		} else {
			for(SymClass sym: syms.subList(i+1, syms.size())) {
				sym.sym = 5;
				sym.f1 = 3;
				sym.f2 = 3;
			}
		}
		return null;
	}
	
	public int getCount() {
		if(count > 0) {
			return count;
		}
		int num = configsHere.size();
		if (children != null) {
			for( ConfigIterator childIterator: children) {
				int childrenSize = childIterator.getCount();
				if ( childrenSize > num) {
					num = childrenSize;
				}
			}
		}
		return num;
	}
	
}
