/*******************************************************************************
 * Copyright (c) 2000, 2004 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials 
 * are made available under the terms of the Common Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/cpl-v10.html
 * 
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.jdt.internal.compiler.parser.diagnose;

import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration;
import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration;
import org.eclipse.jdt.internal.compiler.ast.Initializer;
import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
import org.eclipse.jdt.internal.compiler.lookup.CompilerModifiers;

public class RangeUtil {
	
	// flags
	public static final int NO_FLAG = 0;
	public static final int LBRACE_MISSING = 1;
	public static final int IGNORE = 2;
	
	static class RangeResult {
		private static final int INITIAL_SIZE = 10;
		int pos;
		int[] intervalStarts;
		int[] intervalEnds;
		int[] intervalFlags;
		
		RangeResult() {
			this.pos = 0;
			this.intervalStarts = new int[INITIAL_SIZE];
			this.intervalEnds = new int[INITIAL_SIZE];
			this.intervalFlags = new int[INITIAL_SIZE];
		}
		
		void addInterval(int start, int end){
			addInterval(start, end, NO_FLAG);
		}
		
		void addInterval(int start, int end, int flags){
			if(pos >= intervalStarts.length) {
				System.arraycopy(intervalStarts, 0, intervalStarts = new int[pos * 2], 0, pos);
				System.arraycopy(intervalEnds, 0, intervalEnds = new int[pos * 2], 0, pos);
				System.arraycopy(intervalFlags, 0, intervalFlags = new int[pos * 2], 0, pos);
			}
			intervalStarts[pos] = start;
			intervalEnds[pos] = end;
			intervalFlags[pos] = flags;
			pos++;
		}
		
		int[][] getRanges() {
			int[] resultStarts = new int[pos];
			int[] resultEnds = new int[pos];
			int[] resultFlags = new int[pos];
			
			System.arraycopy(intervalStarts, 0, resultStarts, 0, pos);
			System.arraycopy(intervalEnds, 0, resultEnds, 0, pos);
			System.arraycopy(intervalFlags, 0, resultFlags, 0, pos);

			if (resultStarts.length > 1) {
				quickSort(resultStarts, resultEnds, resultFlags, 0, resultStarts.length - 1);
			}
			return new int[][]{resultStarts, resultEnds, resultFlags};
		}
		
		private void quickSort(int[] list, int[] list2, int[] list3, int left, int right) {
			int original_left= left;
			int original_right= right;
			int mid= list[(left + right) / 2];
			do {
				while (compare(list[left], mid) < 0) {
					left++;
				}
				while (compare(mid, list[right]) < 0) {
					right--;
				}
				if (left <= right) {
					int tmp= list[left];
					list[left]= list[right];
					list[right]= tmp;
					
					tmp = list2[left];
					list2[left]= list2[right];
					list2[right]= tmp;
					
					tmp = list3[left];
					list3[left]= list3[right];
					list3[right]= tmp;
					
					left++;
					right--;
				}
			} while (left <= right);
			
			if (original_left < right) {
				quickSort(list, list2, list3, original_left, right);
			}
			if (left < original_right) {
				quickSort(list, list2, list3, left, original_right);
			}
		}
		
		private int compare(int i1, int i2) {
			return i1 - i2;
		}
	}
	
	
	
	public static boolean containsErrorInSignature(AbstractMethodDeclaration method){
		return method.sourceEnd + 1 == method.bodyStart	|| method.bodyEnd == method.declarationSourceEnd;
	}

	public static int[][] computeDietRange(TypeDeclaration[] types) {
		if(types == null || types.length == 0) {
			return new int[3][0];
		} else {
			RangeResult result = new RangeResult();
			computeDietRange0(types, result);
			return result.getRanges();
		}
	}
	
	private static void computeDietRange0(TypeDeclaration[] types, RangeResult result) {
		for (int j = 0; j < types.length; j++) {
			//members
			TypeDeclaration[] memberTypeDeclarations = types[j].memberTypes;
			if(memberTypeDeclarations != null && memberTypeDeclarations.length > 0) {
				computeDietRange0(types[j].memberTypes, result);
			}
			//methods
			AbstractMethodDeclaration[] methods = types[j].methods;
			if (methods != null) {
				int length = methods.length;
				for (int i = 0; i < length; i++) {
					AbstractMethodDeclaration method = methods[i];
					if(containsIgnoredBody(method)) {
						if(containsErrorInSignature(method)) {
							method.errorInSignature = true;
							result.addInterval(method.declarationSourceStart, method.declarationSourceEnd, IGNORE);
						} else {
							int flags = method.sourceEnd + 1 == method.bodyStart ? LBRACE_MISSING : NO_FLAG;
							result.addInterval(method.bodyStart, method.bodyEnd, flags);
						}
					}
				}
			}
	
			//initializers
			FieldDeclaration[] fields = types[j].fields;
			if (fields != null) {
				int length = fields.length;
				for (int i = 0; i < length; i++) {
					if (fields[i] instanceof Initializer) {
						Initializer initializer = (Initializer)fields[i];
						if(initializer.declarationSourceEnd == initializer.bodyEnd){
							initializer.errorInSignature = true;
							result.addInterval(initializer.declarationSourceStart, initializer.declarationSourceEnd, IGNORE);
						} else {
							result.addInterval(initializer.bodyStart, initializer.bodyEnd);
						}
					}
				}
			}
		}
	}
	
	public static boolean isInInterval(int start, int end, int[] intervalStart, int[] intervalEnd) {
		int length = intervalStart.length;
		for (int i = 0; i < length; i++) {
			if(intervalStart[i] <= start && intervalEnd[i] >= end) {
				return true;
			} else if(intervalStart[i] > end) {
				return false;
			}
		}
		return false;
	}
	
	public static int getPreviousInterval(int start, int end, int[] intervalStart, int[] intervalEnd) {
		int length = intervalStart.length;
		for (int i = 0; i < length; i++) {
			if(intervalStart[i] > end) {
				return i - 1;
			}
		}
		return length - 1;
	}
	
	public static boolean containsIgnoredBody(AbstractMethodDeclaration method){
		return !method.isDefaultConstructor()
			&& !method.isClinit()
			&& (method.modifiers & CompilerModifiers.AccSemicolonBody) == 0;
	}
}
