code: /*
   *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.HashMap;
  import java.util.HashSet;
  import java.util.LinkedList;
  import java.util.List;
  import java.util.Map;
  import java.util.Set;
  
  import se.ewpettersson.admissiblefpg.util.CircularListNode;
  
  public class VertexConfig {
  	
  	// Maps a tet+vertex to a list of other pairs which have been squeezed together.
  	// Note that the entries in the set are formed using the "combine" function.
  	Map<Integer, Set<Integer>> equiv; 
  	
  	public Map<Integer, Set<Integer>> getEquiv() {
  		return equiv;
  	}
  
  	public Map<Integer, Integer> getOnBoundary() {
  		return onBoundary;
  	}
  
  	public Map<Integer, Map<Integer, Map<Integer, CircularListNode>>> getLinks() {
  		return links;
  	}
  
  	// Maps a tet+vertex pair (formed using combine()) to a number indicating how many of the edges about
  	// this tet+vertex pair are on the boundary still.
  	Map<Integer, Integer> onBoundary;
  	Map<Integer, Map<Integer, Map<Integer, CircularListNode>>> links;
  	
  	public void addTetrahedra(int id) {
  		
  		Map<Integer, Map<Integer, CircularListNode>> tetLinks = new HashMap<Integer, Map<Integer, CircularListNode>>();
  		
  		Map<Integer, CircularListNode> vertLink;
  		Set<Integer> vert;
  		for(int i=0; i<4; i++) {
  			vert = new HashSet<Integer>();
  			vert.add(combine( id, i));
  			equiv.put(combine(id, i),vert);
  
  			onBoundary.put(combine(id,i), 3);
  			
  			vertLink = new HashMap<Integer, CircularListNode>();
  			tetLinks.put(i, vertLink);
  			
  		}
  		links.put(id, tetLinks);
  		
  		createTriple(new TVE(id,0,-4), new TVE(id,0,5), new TVE(id,0,-6));
  		createTriple(new TVE(id,1,2), new TVE(id,1,6), new TVE(id,1,-3));
  		createTriple(new TVE(id,2,-1), new TVE(id,2,3), new TVE(id,2,-5));
  		createTriple(new TVE(id,3,4), new TVE(id,3,-2), new TVE(id,3,1));
  		
  	}
  	
  	private void createTriple(TVE tve, TVE tve2, TVE tve3) {
  
  		CircularListNode a = new CircularListNode(tve);
  		CircularListNode b = new CircularListNode(tve2);
  		CircularListNode c = new CircularListNode(tve3);
  		a.setNext(b);
  		b.setNext(c);
  		c.setNext(a);
  		a.setPrev(c);
  		c.setPrev(b);
  		b.setPrev(a);
  		links.get(tve.tet).get(tve.vertex).put(Math.abs(tve.edge),a);
  		links.get(tve2.tet).get(tve2.vertex).put(Math.abs(tve2.edge),b);
  		links.get(tve3.tet).get(tve3.vertex).put(Math.abs(tve3.edge),c);
  	}
  
  	public VertexConfig() {
  		equiv = new HashMap<Integer,Set<Integer>>();
  		links = new HashMap<Integer,Map<Integer,Map<Integer,CircularListNode>>>();
  		onBoundary = new HashMap<Integer, Integer>();
  	}
  	
  	public VertexConfig(VertexConfig vc) {
  		equiv = new HashMap<Integer,Set<Integer>>();
  		links = new HashMap<Integer,Map<Integer,Map<Integer,CircularListNode>>>();
  		onBoundary = new HashMap<Integer, Integer>();
  		
  		// Deep cloning the annoying way.
  		Map<Set<Integer>,Set<Integer>> clnSet = new HashMap<Set<Integer>,Set<Integer>>();
  		for( Integer key: vc.getEquiv().keySet()) {
  			Set<Integer> cloneList = vc.getEquiv().get(key);
  			Set<Integer> newList;
  			if(clnSet.containsKey(cloneList)) {
  				newList = clnSet.get(cloneList);
  			} else {
  				newList = new HashSet<Integer>();
  				for(Integer elt: cloneList) {
  					Integer newInt = new Integer(elt);
  					newList.add(newInt);
  				}
  				clnSet.put(cloneList, newList);
  			}
  			equiv.put(key, newList);
  		}
  		
  		for( Integer key: vc.getOnBoundary().keySet() ) {
  			Integer onBdry = new Integer(vc.getOnBoundary().get(key));
  			onBoundary.put(key, onBdry);
  		}
  		
  		Map<CircularListNode,CircularListNode> clnMap = new HashMap<CircularListNode,CircularListNode>();
  		for( Integer key: vc.getLinks().keySet() ) {
  			Map<Integer,Map<Integer,CircularListNode>> cloneMap = vc.getLinks().get(key);
  			Map<Integer,Map<Integer,CircularListNode>> newMap = new HashMap<Integer,Map<Integer,CircularListNode>>();
  			for( Integer key2: cloneMap.keySet()) {
  				Map<Integer,CircularListNode> cloneMap2 = cloneMap.get(key2);
  				Map<Integer,CircularListNode> newMap2 = new HashMap<Integer,CircularListNode>();
  				for( Integer key3: cloneMap2.keySet()) {
  					CircularListNode node = new CircularListNode(cloneMap2.get(key3));
  					clnMap.put(cloneMap2.get(key3),	node);
  					newMap2.put(key3, node);
  				}
  				newMap.put(key2, newMap2);
  			}
  			links.put(key, newMap);
  		}
  		for(CircularListNode oldNode: clnMap.keySet()) {
  				CircularListNode newNode = clnMap.get(oldNode);
  				newNode.setNext(clnMap.get(oldNode.getNext()));
  				newNode.setPrev(clnMap.get(oldNode.getPrev()));
  		}
  	}
  
  	public boolean addGluing(TVE [] gluing) {
  		CircularListNode linkA = links.get(gluing[0].tet).get(gluing[0].vertex).get(Math.abs(gluing[0].edge));
  		CircularListNode linkB = links.get(gluing[1].tet).get(gluing[1].vertex).get(Math.abs(gluing[1].edge));
  		
  		//System.out.println("Link A:" + linkA);
  		//System.out.println("Link B:" + linkB);
  		//System.out.println("Gluing[0]:" + gluing[0]);
  		//System.out.println("Gluing[1]:" + gluing[1]);
  		// Check to see if the two link-arcs are on the same puncture.
  		boolean samePuncture = false;
  		CircularListNode temp = linkA.getNext();
  		//                              (glue head arcA to tail arcB) == (have we reversed exactly one link-arc)
  		//                              (glue head arcA to tail arcB) != (have we reversed both or neither link-arc)
  		boolean reverseOrient = (gluing[0].edge * gluing[1].edge < 0) != (linkA.data.edge*linkB.data.edge > 0);
  		while ( !temp.equals(linkA) ) {
  			if ( temp.equals(linkB) ) {
  				samePuncture = true;
  				if (reverseOrient) {
  					//if ( linkA != linkB.getNext() && linkA != linkB.getPrev()) {
  						//System.out.println("Mobius strip");
  						return false;
  					//}
  				}
  				break;
  			}
  			temp = temp.getNext();
  		}
  		
  		if (! samePuncture) {
  			// The link-arcs must be on distinct punctures. Check if they're on the same vertex.
  			if ( equiv.get(combine(gluing[0].tet,gluing[0].vertex)).contains( combine( gluing[1].tet,gluing[1].vertex) ) ) {
  				// Link arcs on distinct punctures, but same vertex.  Bad!
  				//System.out.println("Link arcs on distinct punctures, but same vertex.  Bad!");
  				return false;
  			}
  		}
  		
  
  		
  		if (reverseOrient) {
  			temp = linkB.getNext(); // Iterating "forward" along the old orientation.
  			// Reverse all in linkB
  			while (temp != linkB) {
  				temp.data.edge = -temp.data.edge;
  				CircularListNode temp2 = temp.getNext();
  				temp.setNext(temp.getPrev());
  				temp.setPrev(temp2);
  				
  				temp = temp.getPrev(); // We've swapped prev+next, so this is the right direction.
  				
  			}
  			CircularListNode temp2 = linkB.getNext();
  			linkB.setNext(linkB.getPrev());
  			linkB.setPrev(temp2);
  			linkB.data.edge = -temp.data.edge;
  		}
  		CircularListNode prevA = linkA.getPrev();
  		CircularListNode prevB = linkB.getPrev();
  		CircularListNode nextA = linkA.getNext();
  		CircularListNode nextB = linkB.getNext();
  
  //		String a = linkA.toString();
  //		String b = linkB.toString();
  //		String ga = gluing[0].toString();
  //		String gb = gluing[1].toString();
  		
  		// Check to see whether either linkA or linkB represents a puncture consisting of one edge only.
  		boolean linkASolo = (nextA == linkA);
  		boolean linkBSolo = (nextB == linkB);
  		
  		if (linkASolo && linkBSolo) {
  			// Do nothing if both links represent single edge punctures.
  		} else if (linkASolo) {
  			// Just skip the edge linkB if linkA is a single edge puncture.
  			prevB.setNext(nextB);
  			nextB.setPrev(prevB);
  		} else if (linkBSolo) {
  			// Likewise for linkA
  			prevA.setNext(nextA);
  			nextA.setPrev(prevA);
  		} else {
  			// Neither are single edge punctures. Join the two punctures into one puncture.
  			prevA.setNext(nextB);
  			nextB.setPrev(prevA);
  			prevB.setNext(nextA);
  			nextA.setPrev(prevB);
  		}
  		
  		//System.out.println("New link:" + nextA);
  		
  		links.get(gluing[0].tet).get(gluing[0].vertex).remove(Math.abs(gluing[0].edge));
  		links.get(gluing[1].tet).get(gluing[1].vertex).remove(Math.abs(gluing[1].edge));
  		
  		
  		Integer combined0 = combine( gluing[0].tet,gluing[0].vertex);
  		Integer combined1 = combine( gluing[1].tet,gluing[1].vertex);
  
  		equiv.get(combined0).addAll(equiv.get(combined1));
  		for(Integer i:equiv.get(combined1)) {
  			equiv.put(i, equiv.get(combined0));
  		}
  		
  	
  		
  		// Note that each of these tetrahedra/vertex pairs have one less boundary edge.
  		onBoundary.put(combined0,onBoundary.get(combined0)-1);
  		onBoundary.put(combined1,onBoundary.get(combined1)-1);
  
  		boolean vertexClosedOff = false;
  		// Remove them from all equivalence classes if they are no longer on the boundary	
  		if (onBoundary.get(combined0) == 0) {
  			Set<Integer> s = new HashSet<Integer>(equiv.get(combined0));
  			for( Integer i: s ) {
  				Set<Integer> otherList = equiv.get(i);
  				if (otherList != null) {
  					otherList.remove(combined0);
  				}
  				if (otherList.isEmpty()) {
  					// Have finished vertex
  					vertexClosedOff = true;
  				}
  			}
  			equiv.remove(combined0);
  		}
  		
  		
  		// Don't try to remove a second time if combined1==combined0
  		if ((combined1 != combined0) && (onBoundary.get(combined1) == 0)) {
  			Set<Integer> s = new HashSet<Integer>(equiv.get(combined1));
  			for( Integer i: s ) {
  				Set<Integer> otherList = equiv.get(i);
  				if(otherList != null) {
  					otherList.remove(combined1);
  				}
  				if (otherList.isEmpty()) {
  					// Have finished vertex
  					vertexClosedOff = true;
  				}
  			}
  			equiv.remove(combined1);
  		}
  		if (vertexClosedOff && !equiv.isEmpty()) {
  			return false;
  		}
  		return true;
  	}
  	
  	Integer combine(int tet, int vertex) {
  		return 4*tet+vertex;
  	}
  	
  	public String toString() {
  		String s= equiv.toString() + "\n";
  		s+= "[";
  		List<CircularListNode> done = new LinkedList<CircularListNode>();
  		for ( Map<Integer,Map<Integer,CircularListNode>> m1 : links.values()) {
  			for ( Map<Integer, CircularListNode> m2 : m1.values()) {
  				for (CircularListNode n : m2.values()) {
  					if (! done.contains(n)) {
  						s+="[";
  						CircularListNode t = n.getNext();
  						s+= n.toString() + ", ";
  						int i=0;
  						while (t!= null && t != n && i<10) {
  							s+= t.toString() + ", ";
  							done.add(t);
  							t = t.getNext();
  							i++;
  						}
  						s=s.substring(0, s.length()-2)+ "], ";
  						done.add(t);
  					}
  				}
  			}
  		}
  		if ( s.endsWith(", ")) {
  			s=s.substring(0, s.length()-2);
  		}
  		
  		return s+"]";
  	}
  
  	public boolean containsLink(TVE[] link) {
  		CircularListNode linkStart = links.get(link[0].tet).get(link[0].vertex).get(Math.abs(link[0].edge));
  		int linkCounter = 0;
  		if (linkStart.data.edge == link[0].edge) { // Correct direction
  			CircularListNode temp = linkStart.getNext();
  			while ( temp != linkStart ) {
  				linkCounter+=1;
  				if (!link[linkCounter].equals(temp.data)) {
  					return false;
  				}
  				temp = temp.getNext();
  			}
  		} else { // Reverse direction
  			CircularListNode temp = linkStart.getPrev();
  			while ( temp != linkStart ) {
  				linkCounter+=1;
  				link[linkCounter].edge = -link[linkCounter].edge;
  				if (!link[linkCounter].equals(temp.data)) {
  					return false;
  				}
  				temp = temp.getPrev();
  			}
  		}
  		return true;
  	}
  	
  	public boolean equals(VertexConfig other) {
  		// Only need contents of equiv to match up.
  		for( Integer key: equiv.keySet()) {
  			Set<Integer> s = new HashSet<Integer>(equiv.get(key));
  			Set<Integer> s2 = new HashSet<Integer>(other.equiv.get(key));
  			if (! s.equals(s2)) {
  				return false;
  			}
  		}
  		return true;
  	}
  
  	public void mergeWith(VertexConfig vc) {
  		links.putAll(vc.getLinks());
  		equiv.putAll(vc.getEquiv());
  		onBoundary.putAll(vc.getOnBoundary());
  		
  	}
  
  	
  }
  