/*******************************************************************************
 * Copyright (c) 2000, 2003 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 net.sourceforge.phpdt.internal.core.builder;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Date;

import net.sourceforge.phpdt.internal.core.util.SimpleLookupTable;

import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.runtime.IPath;

public class State {
	// NOTE: this state cannot contain types that are not defined in this
	// project

	String javaProjectName;

	ClasspathMultiDirectory[] sourceLocations;

	// ClasspathLocation[] binaryLocations;
	// keyed by the project relative path of the type (ie. "src1/p1/p2/A.java"),
	// value is a ReferenceCollection or an
	// AdditionalTypeCollection
	// SimpleLookupTable references;
	// keyed by qualified type name "p1/p2/A", value is the project relative
	// path which defines this type "src1/p1/p2/A.java"
	// SimpleLookupTable typeLocators;

	int buildNumber;

	long lastStructuralBuildTime;

	SimpleLookupTable structuralBuildTimes;

	private String[] knownPackageNames; // of the form "p1/p2"

	static final byte VERSION = 0x0007;

	static final byte SOURCE_FOLDER = 1;

	// static final byte BINARY_FOLDER = 2;
	// static final byte EXTERNAL_JAR = 3;
	// static final byte INTERNAL_JAR = 4;

	State() {
	}

	protected State(PHPBuilder javaBuilder) {
		this.knownPackageNames = null;
		this.javaProjectName = javaBuilder.currentProject.getName();
		this.sourceLocations = javaBuilder.nameEnvironment.sourceLocations;
		// this.binaryLocations = javaBuilder.nameEnvironment.binaryLocations;
		// this.references = new SimpleLookupTable(7);
		// this.typeLocators = new SimpleLookupTable(7);

		this.buildNumber = 0; // indicates a full build
		this.lastStructuralBuildTime = System.currentTimeMillis();
		this.structuralBuildTimes = new SimpleLookupTable(3);
	}

	void copyFrom(State lastState) {
		// try {
		this.knownPackageNames = null;
		this.buildNumber = lastState.buildNumber + 1;
		this.lastStructuralBuildTime = lastState.lastStructuralBuildTime;
		// this.references = (SimpleLookupTable) lastState.references.clone();
		// this.typeLocators = (SimpleLookupTable)
		// lastState.typeLocators.clone();
		// } catch (CloneNotSupportedException e) {
		// this.references = new
		// SimpleLookupTable(lastState.references.elementSize);
		// Object[] keyTable = lastState.references.keyTable;
		// Object[] valueTable = lastState.references.valueTable;
		// for (int i = 0, l = keyTable.length; i < l; i++)
		// if (keyTable[i] != null)
		// this.references.put(keyTable[i], valueTable[i]);
		//
		// this.typeLocators = new
		// SimpleLookupTable(lastState.typeLocators.elementSize);
		// keyTable = lastState.typeLocators.keyTable;
		// valueTable = lastState.typeLocators.valueTable;
		// for (int i = 0, l = keyTable.length; i < l; i++)
		// if (keyTable[i] != null)
		// this.typeLocators.put(keyTable[i], valueTable[i]);
		// }
	}

	// char[][] getDefinedTypeNamesFor(String typeLocator) {
	// Object c = references.get(typeLocator);
	// if (c instanceof AdditionalTypeCollection)
	// return ((AdditionalTypeCollection) c).definedTypeNames;
	// return null; // means only one type is defined with the same name as the
	// file... saves space
	// }

	// boolean isDuplicateLocator(String qualifiedTypeName, String typeLocator)
	// {
	// String existing = (String) typeLocators.get(qualifiedTypeName);
	// return existing != null && !existing.equals(typeLocator);
	// }

	// boolean isKnownPackage(String qualifiedPackageName) {
	// if (knownPackageNames == null) {
	// ArrayList names = new ArrayList(typeLocators.elementSize);
	// Object[] keyTable = typeLocators.keyTable;
	// for (int i = 0, l = keyTable.length; i < l; i++) {
	// if (keyTable[i] != null) {
	// String packageName = (String) keyTable[i]; // is a type name of the form
	// p1/p2/A
	// int last = packageName.lastIndexOf('/');
	// packageName = last == -1 ? null : packageName.substring(0, last);
	// while (packageName != null && !names.contains(packageName)) {
	// names.add(packageName);
	// last = packageName.lastIndexOf('/');
	// packageName = last == -1 ? null : packageName.substring(0, last);
	// }
	// }
	// }
	// knownPackageNames = new String[names.size()];
	// names.toArray(knownPackageNames);
	// }
	// for (int i = 0, l = knownPackageNames.length; i < l; i++)
	// if (knownPackageNames[i].equals(qualifiedPackageName))
	// return true;
	// return false;
	// }

	// void record(String typeLocator, char[][][] qualifiedRefs, char[][]
	// simpleRefs, char[] mainTypeName, ArrayList typeNames) {
	// if (typeNames.size() == 1 && CharOperation.equals(mainTypeName, (char[])
	// typeNames.get(0))) {
	// references.put(typeLocator, new ReferenceCollection(qualifiedRefs,
	// simpleRefs));
	// } else {
	// char[][] definedTypeNames = new char[typeNames.size()][]; // can be empty
	// when no types are defined
	// typeNames.toArray(definedTypeNames);
	// references.put(typeLocator, new
	// AdditionalTypeCollection(definedTypeNames, qualifiedRefs, simpleRefs));
	// }
	// }
	//
	// void recordLocatorForType(String qualifiedTypeName, String typeLocator) {
	// this.knownPackageNames = null;
	// typeLocators.put(qualifiedTypeName, typeLocator);
	// }

	void recordStructuralDependency(IProject prereqProject, State prereqState) {
		if (prereqState != null)
			structuralBuildTimes.put(prereqProject.getName(), new Long(
					prereqState.lastStructuralBuildTime));
	}

	// void removeLocator(String typeLocatorToRemove) {
	// this.knownPackageNames = null;
	// references.removeKey(typeLocatorToRemove);
	// typeLocators.removeValue(typeLocatorToRemove);
	// }

	void removePackage(IResourceDelta sourceDelta) {
		IResource resource = sourceDelta.getResource();
		switch (resource.getType()) {
		case IResource.FOLDER:
			IResourceDelta[] children = sourceDelta.getAffectedChildren();
			for (int i = 0, l = children.length; i < l; i++)
				removePackage(children[i]);
			return;
		case IResource.FILE:
			IPath typeLocatorPath = resource.getProjectRelativePath();
			// if
			// (ProjectPrefUtil.isJavaFileName(typeLocatorPath.lastSegment()))
			// removeLocator(typeLocatorPath.toString());
		}
	}

	// void removeQualifiedTypeName(String qualifiedTypeNameToRemove) {
	// this.knownPackageNames = null;
	// typeLocators.removeKey(qualifiedTypeNameToRemove);
	// }

	static State read(IProject project, DataInputStream in) throws IOException {
		if (PHPBuilder.DEBUG)
			System.out.println("About to read state..."); //$NON-NLS-1$
		if (VERSION != in.readByte()) {
			if (PHPBuilder.DEBUG)
				System.out
						.println("Found non-compatible state version... answered null"); //$NON-NLS-1$
			return null;
		}

		State newState = new State();
		newState.javaProjectName = in.readUTF();
		if (!project.getName().equals(newState.javaProjectName)) {
			if (PHPBuilder.DEBUG)
				System.out
						.println("Project's name does not match... answered null"); //$NON-NLS-1$
			return null;
		}
		newState.buildNumber = in.readInt();
		newState.lastStructuralBuildTime = in.readLong();

		int length;
		// int length = in.readInt();
		// newState.sourceLocations = new ClasspathMultiDirectory[0];
		// newState.sourceLocations = new ClasspathMultiDirectory[length];
		// for (int i = 0; i < length; i++) {
		// IContainer sourceFolder = project, outputFolder = project;
		// String folderName;
		// if ((folderName = in.readUTF()).length() > 0) sourceFolder =
		// project.getFolder(folderName);
		// if ((folderName = in.readUTF()).length() > 0) outputFolder =
		// project.getFolder(folderName);
		// ClasspathMultiDirectory md =
		// (ClasspathMultiDirectory)
		// ClasspathLocation.forSourceFolder(sourceFolder, outputFolder,
		// readNames(in));
		// if (in.readBoolean())
		// md.hasIndependentOutputFolder = true;
		// newState.sourceLocations[i] = md;
		// }

		// length = in.readInt();
		// newState.binaryLocations = new ClasspathLocation[length];
		// IWorkspaceRoot root = project.getWorkspace().getRoot();
		// for (int i = 0; i < length; i++) {
		// switch (in.readByte()) {
		// case SOURCE_FOLDER :
		// newState.binaryLocations[i] = newState.sourceLocations[in.readInt()];
		// break;
		// case BINARY_FOLDER :
		// IPath path = new Path(in.readUTF());
		// IContainer outputFolder = path.segmentCount() == 1
		// ? (IContainer) root.getProject(path.toString())
		// : (IContainer) root.getFolder(path);
		// newState.binaryLocations[i] =
		// ClasspathLocation.forBinaryFolder(outputFolder, in.readBoolean());
		// break;
		// case EXTERNAL_JAR :
		// newState.binaryLocations[i] =
		// ClasspathLocation.forLibrary(in.readUTF());
		// break;
		// case INTERNAL_JAR :
		// newState.binaryLocations[i] =
		// ClasspathLocation.forLibrary(root.getFile(new Path(in.readUTF())));
		// }
		// }

		newState.structuralBuildTimes = new SimpleLookupTable(length = in
				.readInt());
		for (int i = 0; i < length; i++)
			newState.structuralBuildTimes.put(in.readUTF(), new Long(in
					.readLong()));

		// String[] internedTypeLocators = new String[length = in.readInt()];
		// for (int i = 0; i < length; i++)
		// internedTypeLocators[i] = in.readUTF();

		// newState.typeLocators = new SimpleLookupTable(length = in.readInt());
		// for (int i = 0; i < length; i++)
		// newState.typeLocators.put(in.readUTF(),
		// internedTypeLocators[in.readInt()]);

		// char[][] internedSimpleNames =
		// ReferenceCollection.internSimpleNames(readNames(in), false);
		// char[][][] internedQualifiedNames = new char[length =
		// in.readInt()][][];
		// for (int i = 0; i < length; i++) {
		// int qLength = in.readInt();
		// char[][] qName = new char[qLength][];
		// for (int j = 0; j < qLength; j++)
		// qName[j] = internedSimpleNames[in.readInt()];
		// internedQualifiedNames[i] = qName;
		// }
		// internedQualifiedNames =
		// ReferenceCollection.internQualifiedNames(internedQualifiedNames);
		//
		// newState.references = new SimpleLookupTable(length = in.readInt());
		// for (int i = 0; i < length; i++) {
		// String typeLocator = internedTypeLocators[in.readInt()];
		// ReferenceCollection collection = null;
		// switch (in.readByte()) {
		// case 1 :
		// char[][] additionalTypeNames = readNames(in);
		// char[][][] qualifiedNames = new char[in.readInt()][][];
		// for (int j = 0, m = qualifiedNames.length; j < m; j++)
		// qualifiedNames[j] = internedQualifiedNames[in.readInt()];
		// char[][] simpleNames = new char[in.readInt()][];
		// for (int j = 0, m = simpleNames.length; j < m; j++)
		// simpleNames[j] = internedSimpleNames[in.readInt()];
		// collection = new AdditionalTypeCollection(additionalTypeNames,
		// qualifiedNames, simpleNames);
		// break;
		// case 2 :
		// char[][][] qNames = new char[in.readInt()][][];
		// for (int j = 0, m = qNames.length; j < m; j++)
		// qNames[j] = internedQualifiedNames[in.readInt()];
		// char[][] sNames = new char[in.readInt()][];
		// for (int j = 0, m = sNames.length; j < m; j++)
		// sNames[j] = internedSimpleNames[in.readInt()];
		// collection = new ReferenceCollection(qNames, sNames);
		// }
		// newState.references.put(typeLocator, collection);
		// }
		if (PHPBuilder.DEBUG)
			System.out
					.println("Successfully read state for " + newState.javaProjectName); //$NON-NLS-1$
		return newState;
	}

	private static char[][] readNames(DataInputStream in) throws IOException {
		int length = in.readInt();
		char[][] names = new char[length][];
		for (int i = 0; i < length; i++) {
			int nLength = in.readInt();
			char[] name = new char[nLength];
			for (int j = 0; j < nLength; j++)
				name[j] = in.readChar();
			names[i] = name;
		}
		return names;
	}

	void tagAsNoopBuild() {
		this.buildNumber = -1; // tag the project since it has no source
								// folders and can be skipped
	}

	boolean wasNoopBuild() {
		return buildNumber == -1;
	}

	void tagAsStructurallyChanged() {
		this.lastStructuralBuildTime = System.currentTimeMillis();
	}

	boolean wasStructurallyChanged(IProject prereqProject, State prereqState) {
		if (prereqState != null) {
			Object o = structuralBuildTimes.get(prereqProject.getName());
			long previous = o == null ? 0 : ((Long) o).longValue();
			if (previous == prereqState.lastStructuralBuildTime)
				return false;
		}
		return true;
	}

	void write(DataOutputStream out) throws IOException {
		int length;
		Object[] keyTable;
		Object[] valueTable;

		/*
		 * byte VERSION String project name int build number int last structural
		 * build number
		 */
		out.writeByte(VERSION);
		out.writeUTF(javaProjectName);
		out.writeInt(buildNumber);
		out.writeLong(lastStructuralBuildTime);

		/*
		 * ClasspathMultiDirectory[] int id String path(s)
		 */
		// out.writeInt(length = sourceLocations.length);
		// for (int i = 0; i < length; i++) {
		// ClasspathMultiDirectory md = sourceLocations[i];
		// out.writeUTF(md.sourceFolder.getProjectRelativePath().toString());
		// out.writeUTF(md.binaryFolder.getProjectRelativePath().toString());
		// writeNames(md.exclusionPatterns, out);
		// out.writeBoolean(md.hasIndependentOutputFolder);
		// }
		/*
		 * ClasspathLocation[] int id String path(s)
		 */
		// out.writeInt(length = binaryLocations.length);
		// next : for (int i = 0; i < length; i++) {
		// ClasspathLocation c = binaryLocations[i];
		// if (c instanceof ClasspathMultiDirectory) {
		// out.writeByte(SOURCE_FOLDER);
		// for (int j = 0, m = sourceLocations.length; j < m; j++) {
		// if (sourceLocations[j] == c) {
		// out.writeInt(j);
		// continue next;
		// }
		// }
		// } else if (c instanceof ClasspathDirectory) {
		// out.writeByte(BINARY_FOLDER);
		// ClasspathDirectory cd = (ClasspathDirectory) c;
		// out.writeUTF(cd.binaryFolder.getFullPath().toString());
		// out.writeBoolean(cd.isOutputFolder);
		// } else {
		// ClasspathJar jar = (ClasspathJar) c;
		// if (jar.resource == null) {
		// out.writeByte(EXTERNAL_JAR);
		// out.writeUTF(jar.zipFilename);
		// } else {
		// out.writeByte(INTERNAL_JAR);
		// out.writeUTF(jar.resource.getFullPath().toString());
		// }
		// }
		// }
		/*
		 * Structural build numbers table String prereq project name int last
		 * structural build number
		 */
		out.writeInt(length = structuralBuildTimes.elementSize);
		if (length > 0) {
			keyTable = structuralBuildTimes.keyTable;
			valueTable = structuralBuildTimes.valueTable;
			for (int i = 0, l = keyTable.length; i < l; i++) {
				if (keyTable[i] != null) {
					length--;
					out.writeUTF((String) keyTable[i]);
					out.writeLong(((Long) valueTable[i]).longValue());
				}
			}
			if (PHPBuilder.DEBUG && length != 0)
				System.out
						.println("structuralBuildNumbers table is inconsistent"); //$NON-NLS-1$
		}

		/*
		 * String[] Interned type locators
		 */
		// out.writeInt(length = references.elementSize);
		// ArrayList internedTypeLocators = new ArrayList(length);
		// if (length > 0) {
		// keyTable = references.keyTable;
		// for (int i = 0, l = keyTable.length; i < l; i++) {
		// if (keyTable[i] != null) {
		// length--;
		// String key = (String) keyTable[i];
		// out.writeUTF(key);
		// internedTypeLocators.add(key);
		// }
		// }
		// if (PHPBuilder.DEBUG && length != 0)
		// System.out.println("references table is inconsistent"); //$NON-NLS-1$
		// }
		/*
		 * Type locators table String type name int interned locator id
		 */
		// out.writeInt(length = typeLocators.elementSize);
		// if (length > 0) {
		// keyTable = typeLocators.keyTable;
		// valueTable = typeLocators.valueTable;
		// for (int i = 0, l = keyTable.length; i < l; i++) {
		// if (keyTable[i] != null) {
		// length--;
		// out.writeUTF((String) keyTable[i]);
		// out.writeInt(internedTypeLocators.indexOf((String) valueTable[i]));
		// }
		// }
		// if (PHPBuilder.DEBUG && length != 0)
		// System.out.println("typeLocators table is inconsistent");
		// //$NON-NLS-1$
		// }
		/*
		 * char[][][] Interned qualified names char[][] Interned simple names
		 */
		// ArrayList internedQualifiedNames = new ArrayList(31);
		// ArrayList internedSimpleNames = new ArrayList(31);
		// valueTable = references.valueTable;
		// for (int i = 0, l = valueTable.length; i < l; i++) {
		// if (valueTable[i] != null) {
		// ReferenceCollection collection = (ReferenceCollection) valueTable[i];
		// char[][][] qNames = collection.qualifiedNameReferences;
		// for (int j = 0, m = qNames.length; j < m; j++) {
		// char[][] qName = qNames[j];
		// if (!internedQualifiedNames.contains(qName)) { // remember the names
		// have been interned
		// internedQualifiedNames.add(qName);
		// for (int k = 0, n = qName.length; k < n; k++) {
		// char[] sName = qName[k];
		// if (!internedSimpleNames.contains(sName)) // remember the names have
		// been interned
		// internedSimpleNames.add(sName);
		// }
		// }
		// }
		// char[][] sNames = collection.simpleNameReferences;
		// for (int j = 0, m = sNames.length; j < m; j++) {
		// char[] sName = sNames[j];
		// if (!internedSimpleNames.contains(sName)) // remember the names have
		// been interned
		// internedSimpleNames.add(sName);
		// }
		// }
		// }
		// char[][] internedArray = new char[internedSimpleNames.size()][];
		// internedSimpleNames.toArray(internedArray);
		// writeNames(internedArray, out);
		// // now write the interned qualified names as arrays of interned
		// simple names
		// out.writeInt(length = internedQualifiedNames.size());
		// for (int i = 0; i < length; i++) {
		// char[][] qName = (char[][]) internedQualifiedNames.get(i);
		// int qLength = qName.length;
		// out.writeInt(qLength);
		// for (int j = 0; j < qLength; j++)
		// out.writeInt(internedSimpleNames.indexOf(qName[j]));
		// }
		//
		// /*
		// * References table int interned locator id ReferenceCollection
		// */
		// out.writeInt(length = references.elementSize);
		// if (length > 0) {
		// keyTable = references.keyTable;
		// for (int i = 0, l = keyTable.length; i < l; i++) {
		// if (keyTable[i] != null) {
		// length--;
		// out.writeInt(internedTypeLocators.indexOf((String) keyTable[i]));
		// ReferenceCollection collection = (ReferenceCollection) valueTable[i];
		// if (collection instanceof AdditionalTypeCollection) {
		// out.writeByte(1);
		// AdditionalTypeCollection atc = (AdditionalTypeCollection) collection;
		// writeNames(atc.definedTypeNames, out);
		// } else {
		// out.writeByte(2);
		// }
		// char[][][] qNames = collection.qualifiedNameReferences;
		// int qLength = qNames.length;
		// out.writeInt(qLength);
		// for (int j = 0; j < qLength; j++)
		// out.writeInt(internedQualifiedNames.indexOf(qNames[j]));
		// char[][] sNames = collection.simpleNameReferences;
		// int sLength = sNames.length;
		// out.writeInt(sLength);
		// for (int j = 0; j < sLength; j++)
		// out.writeInt(internedSimpleNames.indexOf(sNames[j]));
		// }
		// }
		// if (PHPBuilder.DEBUG && length != 0)
		// System.out.println("references table is inconsistent"); //$NON-NLS-1$
		// }
	}

	private void writeNames(char[][] names, DataOutputStream out)
			throws IOException {
		int length = names == null ? 0 : names.length;
		out.writeInt(length);
		for (int i = 0; i < length; i++) {
			char[] name = names[i];
			int nLength = name.length;
			out.writeInt(nLength);
			for (int j = 0; j < nLength; j++)
				out.writeChar(name[j]);
		}
	}

	/**
	 * Returns a string representation of the receiver.
	 */
	public String toString() {
		return "State for " + javaProjectName //$NON-NLS-1$
				+ " (#" + buildNumber //$NON-NLS-1$
				+ " @ " + new Date(lastStructuralBuildTime) //$NON-NLS-1$
				+ ")"; //$NON-NLS-1$
	}

	/*
	 * Debug helper void dump() { System.out.println("State for " +
	 * javaProjectName + " (" + buildNumber + " @ " + new
	 * Date(lastStructuralBuildTime) + ")"); System.out.println("\tClass path
	 * source locations:"); for (int i = 0, l = sourceLocations.length; i < l;
	 * i++) System.out.println("\t\t" + sourceLocations[i]);
	 * System.out.println("\tClass path binary locations:"); for (int i = 0, l =
	 * binaryLocations.length; i < l; i++) System.out.println("\t\t" +
	 * binaryLocations[i]);
	 * 
	 * System.out.print("\tStructural build numbers table:"); if
	 * (structuralBuildTimes.elementSize == 0) { System.out.print(" <empty> "); }
	 * else { Object[] keyTable = structuralBuildTimes.keyTable; Object[]
	 * valueTable = structuralBuildTimes.valueTable; for (int i = 0, l =
	 * keyTable.length; i < l; i++) if (keyTable[i] != null)
	 * System.out.print("\n\t\t" + keyTable[i].toString() + " -> " +
	 * valueTable[i].toString()); }
	 * 
	 * System.out.print("\tType locators table:"); if (typeLocators.elementSize ==
	 * 0) { System.out.print(" <empty> "); } else { Object[] keyTable =
	 * typeLocators.keyTable; Object[] valueTable = typeLocators.valueTable; for
	 * (int i = 0, l = keyTable.length; i < l; i++) if (keyTable[i] != null)
	 * System.out.print("\n\t\t" + keyTable[i].toString() + " -> " +
	 * valueTable[i].toString()); }
	 * 
	 * System.out.print("\n\tReferences table:"); if (references.elementSize ==
	 * 0) { System.out.print(" <empty> "); } else { Object[] keyTable =
	 * references.keyTable; Object[] valueTable = references.valueTable; for
	 * (int i = 0, l = keyTable.length; i < l; i++) { if (keyTable[i] != null) {
	 * System.out.print("\n\t\t" + keyTable[i].toString()); ReferenceCollection
	 * c = (ReferenceCollection) valueTable[i]; char[][][] qRefs =
	 * c.qualifiedNameReferences; System.out.print("\n\t\t\tqualified:"); if
	 * (qRefs.length == 0) System.out.print(" <empty> "); else for (int j = 0, m =
	 * qRefs.length; j < m; j++) System.out.print(" '" +
	 * CharOperation.toString(qRefs[j]) + "'"); char[][] sRefs =
	 * c.simpleNameReferences; System.out.print("\n\t\t\tsimple:"); if
	 * (sRefs.length == 0) System.out.print(" <empty> "); else for (int j = 0, m =
	 * sRefs.length; j < m; j++) System.out.print(" " + new String(sRefs[j]));
	 * if (c instanceof AdditionalTypeCollection) { char[][] names =
	 * ((AdditionalTypeCollection) c).definedTypeNames;
	 * System.out.print("\n\t\t\tadditional type names:"); for (int j = 0, m =
	 * names.length; j < m; j++) System.out.print(" " + new String(names[j])); } } } }
	 * System.out.print("\n\n"); }
	 */
}
