/*
 *    Fernflower - The Analytical Java Decompiler
 *    http://www.reversed-java.com
 *
 *    (C) 2008 - 2010, Stiver
 *
 *    This software is NEITHER public domain NOR free software 
 *    as per GNU License. See license.txt for more details.
 *
 *    This software is distributed WITHOUT ANY WARRANTY; without 
 *    even the implied warranty of MERCHANTABILITY or FITNESS FOR 
 *    A PARTICULAR PURPOSE. 
 */

package de.fernflower.modules.decompiler;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;

import de.fernflower.code.cfg.BasicBlock;
import de.fernflower.main.DecompilerContext;
import de.fernflower.main.collectors.CounterContainer;
import de.fernflower.modules.decompiler.exps.Exprent;
import de.fernflower.modules.decompiler.stats.BasicBlockStatement;
import de.fernflower.modules.decompiler.stats.SequenceStatement;
import de.fernflower.modules.decompiler.stats.Statement;


public class SequenceHelper {


	public static void condenseSequences(Statement root) {
		condenseSequencesRec(root);
	}
		
	private static void condenseSequencesRec(Statement stat) {
		
		if(stat.type == Statement.TYPE_SEQUENCE) {

			List<Statement> lst = new ArrayList<Statement>();
			lst.addAll(stat.getStats());
			
			boolean unfolded = false;
			
			// unfold blocks
			for(int i=0;i<lst.size();i++) {
				Statement st = lst.get(i);
				if(st.type == Statement.TYPE_SEQUENCE) {
					
					removeEmptyStatements((SequenceStatement)st);
					
					if(i == lst.size()-1 || isSequenceDisbandable(st, lst.get(i+1))) {
						// move predecessors
						Statement first = st.getFirst();
						for(StatEdge edge: st.getAllPredecessorEdges()) {
							st.removePredecessor(edge);
							edge.getSource().changeEdgeNode(Statement.DIRECTION_FORWARD, edge, first);
							first.addPredecessor(edge);
						}
						
						// move successors
						Statement last = st.getStats().getLast();
						if(last.getAllSuccessorEdges().isEmpty() && i<lst.size()-1) {
							last.addSuccessor(new StatEdge(StatEdge.TYPE_REGULAR, last, lst.get(i+1)));
						} else {
							for(StatEdge edge: last.getAllSuccessorEdges()) {
								if(i == lst.size()-1) {
									if(edge.closure == st) {
										stat.addLabeledEdge(edge);
									}
								} else {
									edge.getSource().changeEdgeType(Statement.DIRECTION_FORWARD, edge, StatEdge.TYPE_REGULAR);
									edge.closure.getLabelEdges().remove(edge);
									edge.closure = null;
								}
							}
						}
						
						for(StatEdge edge : st.getAllSuccessorEdges()) {
							st.removeSuccessor(edge);
						}
						
						for(StatEdge edge: new HashSet<StatEdge>(st.getLabelEdges())) {
							if(edge.getSource() != last) {
								last.addLabeledEdge(edge);
							}
						}
						
						lst.remove(i);
						lst.addAll(i, st.getStats());
						i--;
						
						unfolded = true;
					}
				}
			}
		
			if(unfolded) {
				SequenceStatement sequence = new SequenceStatement(lst);
				sequence.setAllParent();

				stat.getParent().replaceStatement(stat, sequence);
				
				stat = sequence;
			} 
		}
		
		// sequence consisting of one statement -> disband
		if(stat.type == Statement.TYPE_SEQUENCE) { 
			
			removeEmptyStatements((SequenceStatement)stat);
			
			if(stat.getStats().size() == 1) {
				
				Statement st = stat.getFirst();
				
				boolean ok = st.getAllSuccessorEdges().isEmpty();
				if(!ok) {
					StatEdge edge = st.getAllSuccessorEdges().get(0);
					
					ok = stat.getAllSuccessorEdges().isEmpty();
					if(!ok) {
						StatEdge statedge = stat.getAllSuccessorEdges().get(0);
						ok = (edge.getDestination() == statedge.getDestination());

						if(ok) {
							st.removeSuccessor(edge);
						}
					}
				}
				
				if(ok) {
					stat.getParent().replaceStatement(stat, st);
					stat = st;
				}
			}
		}
		
		// replace flat statements with synthetic basic blocks 
		outer:
		for(;;) {
			for(Statement st: stat.getStats()) {
				if((st.getStats().isEmpty() || st.getExprents() != null) && st.type != Statement.TYPE_BASICBLOCK) {
					destroyAndFlattenStatement(st);
					continue outer;
				}
			}
			break;
		} 
		
		// recursion
		for(int i=0;i<stat.getStats().size();i++) {
			condenseSequencesRec(stat.getStats().get(i));
		}
		
	}
	
	private static boolean isSequenceDisbandable(Statement block, Statement next) {
		
		Statement last = block.getStats().getLast();
		List<StatEdge> lstSuccs = last.getAllSuccessorEdges();
		if(!lstSuccs.isEmpty()) {
			if(lstSuccs.get(0).getDestination() != next) {
				return false;
			}
		}
		
		for(StatEdge edge : next.getPredecessorEdges(StatEdge.TYPE_BREAK)) {
			if(last != edge.getSource() && !last.containsStatementStrict(edge.getSource())) {
				return false;
			}				
		}
		
		return true;
	}
	
	private static void removeEmptyStatements(SequenceStatement sequence) {
		
		if(sequence.getStats().size() <= 1) {
			return;
		}
		
		mergeFlatStatements(sequence);
		
		for(;;) {
			
			boolean found = false;
			
			for(Statement st: sequence.getStats()) {
				
				if(st.getExprents() != null && st.getExprents().isEmpty()) {
					
					if(st.getAllSuccessorEdges().isEmpty()) {
						List<StatEdge> lstBreaks = st.getPredecessorEdges(StatEdge.TYPE_BREAK);
						
						if(lstBreaks.isEmpty()) {
							for(StatEdge edge: st.getAllPredecessorEdges()) {
								edge.getSource().removeSuccessor(edge);
							}
							found = true;
						}
					} else {
						StatEdge sucedge = st.getAllSuccessorEdges().get(0);
						if(sucedge.getType() != StatEdge.TYPE_FINALLYEXIT) {
							st.removeSuccessor(sucedge);

							for(StatEdge edge: st.getAllPredecessorEdges()) {
								if(sucedge.getType()!=StatEdge.TYPE_REGULAR) {
									edge.getSource().changeEdgeType(Statement.DIRECTION_FORWARD, edge, sucedge.getType());
								}
								
								st.removePredecessor(edge);
								edge.getSource().changeEdgeNode(Statement.DIRECTION_FORWARD, edge, sucedge.getDestination());
								sucedge.getDestination().addPredecessor(edge);

								if(sucedge.closure != null) {
									sucedge.closure.addLabeledEdge(edge);
								}
							}
							found = true;
						}
					}
					
					if(found) {
						sequence.getStats().removeWithKey(st.id);
						break;
					}
				}
			}
			
			if(!found) {
				break;
			}
		}
		
		sequence.setFirst(sequence.getStats().get(0));
		
	}
	
	private static void mergeFlatStatements(SequenceStatement sequence) {

		for(;;) {

			Statement next = null;
			Statement current = null;
			
			boolean found = false;
			
			for(int i=sequence.getStats().size()-1;i>=0;i--) {
				
				next = current;
				current = sequence.getStats().get(i);
				
				if(next != null && current.getExprents()!=null && !current.getExprents().isEmpty()) {
					if(next.getExprents()!=null) {
						next.getExprents().addAll(0, current.getExprents());
						current.getExprents().clear();
						found = true;
					} else {
						Statement first = getFirstExprentlist(next);
						if(first != null) {
							first.getExprents().addAll(0, current.getExprents());
							current.getExprents().clear();
							found = true;
						}
					}
				}
			}
			
			if(!found) {
				break;
			}
		}
		
	}
	
	private static Statement getFirstExprentlist(Statement stat) {
	
		if(stat.getExprents() != null) {
			return stat;
		}
		
		switch(stat.type) {
		case Statement.TYPE_IF:
		case Statement.TYPE_SEQUENCE:
		case Statement.TYPE_SWITCH:
		case Statement.TYPE_SYNCRONIZED:
			return getFirstExprentlist(stat.getFirst());
		}
		
		return null;
	}
	
	
	public static void destroyAndFlattenStatement(Statement stat) {
		
		destroyStatementContent(stat, false);
		
		BasicBlockStatement bstat = new BasicBlockStatement(new BasicBlock(
				DecompilerContext.getCountercontainer().getCounterAndIncrement(CounterContainer.STATEMENT_COUNTER)));
		if(stat.getExprents() == null) {
			bstat.setExprents(new ArrayList<Exprent>());
		} else {
			bstat.setExprents(DecHelper.copyExprentList(stat.getExprents()));
		}
		
		stat.getParent().replaceStatement(stat, bstat);
	}
	
	public static void destroyStatementContent(Statement stat, boolean self) {
		
		for(Statement st: stat.getStats()) {
			destroyStatementContent(st, true);
		}
		stat.getStats().clear();

		if(self) {
			for(StatEdge edge : stat.getAllSuccessorEdges()) {
				stat.removeSuccessor(edge);
			}
		}
		
	}
	
}
