/*
* Scrabaid
*
* Revisions:
*
* 18/05/2001 lc - v1.0 release
*
* ----------------------------------------------------------------------------
*
* Scrabaid version 1.0, Copyright 2001 Linus Chang
* Scrabaid comes with ABSOLUTELY NO WARRANTY; for details, view the LICENCE
* file included with this distribution. This is free software, and you are
* welcome to redistribute it under certain conditions; view the LICENCE
* file for details.
*/


import java.io.*;
import java.util.*;

class PatternCollection {
	private Vector m_pending = new Vector();
	private File m_directory;
	private RandomAccessFile m_randomAccessFile;
	private boolean m_readOnly;
	private String m_name;
	private short[] m_lengths;	// The length of matches for each hashcode. 
								// eg. m_lengths[4] = 3 means there are 3 
								// words that match pattern hash 4
	private int[] m_offsets;	// The offsets for each hashcode.

	private void initialiseReadOnly() throws ScrabaidException {
		m_readOnly = true;
		m_lengths = new short[Pattern.MAX_HASH];
		m_offsets = new int[Pattern.MAX_HASH];
		// Check to see if the file already exists. If not, throw an exception
		File file = new File(m_directory, m_name + ".dat");
		try {
			System.out.print("Opening pattern collection... " + file);
			FileInputStream fis = new FileInputStream(file);
			System.out.println(" loading.");
			try {
				DataInputStream dis = new DataInputStream(new BufferedInputStream(fis));
				for (int c = 0; c < Pattern.MAX_HASH; c++) {
					m_lengths[c] = dis.readShort();
				}
				fis.close();
			}
			catch (IOException ioe) {
				throw new ScrabaidException("IOException caught during the reading of the PatternCollection file " + file);
			}
		}
		catch (FileNotFoundException fnfe) {
			throw new ScrabaidException("The Pattern Collection file " + file.getPath() + " does not exist.");
		}
		calculateOffsets();
	}

	private void initialiseWriteOnly() throws ScrabaidException {
		// Check to see if the file already exists. If so, delete it, and write
		// a new empty file.
		File file = new File(m_directory, m_name + ".dat");
		try {
			if (file.isFile()) {
				System.out.println("Pattern collection file " + file.getPath() + " already exists. Deleting...");
				file.delete();
			}
			FileOutputStream fos = new FileOutputStream(file);
			DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(fos));
			for (int c = 0; c < Pattern.MAX_HASH; c++) {
				dos.writeShort(0);
			}
			dos.close();
		}
		catch (IOException ioe) {
				throw new ScrabaidException("IOException caught during the writing of a new PatternCollection file " + file);
		}
	}
	public PatternCollection(File directory, String name, boolean readOnly) throws ScrabaidException {
		m_directory = directory;
		m_name = name;

		if (readOnly)
			initialiseReadOnly();
		else
			initialiseWriteOnly();
	}
	private void calculateOffsets() {
		int index = 0;
		for (int c = 0; c < m_lengths.length; c++) {
			m_offsets[c] = index;
			index += m_lengths[c];
		}
	}

	public void addPattern(Pattern p, short index) {
		int[] iArray = new int[2];
		iArray[0] = p.getCustomHash();
		iArray[1] = index;
		m_pending.add(iArray);
	}
	public void addPatterns(Pattern[] p, short index) {
		for (int c = 0; c < p.length; c++)
			addPattern(p[c], index);
	}
	public int getNumPending() {
		return m_pending.size();
	}
	public short[] getPatternIndices(Pattern p) throws ScrabaidException {
		if (!m_readOnly) {
			throw new ScrabaidException("PatternCollection must be opened as read only to search for patterns.");
		}
		// Return a list of indices of words that match pattern p
		try {
			// Open the old file....
			File file = new File(m_directory, m_name + ".dat");
			RandomAccessFile raf = new RandomAccessFile(file, "r");

			// Find the index of the required pattern
			long hashcode = p.getCustomHash();

			// Allocate the return array
			short[] wordIndices = new short[m_lengths[(int)hashcode]];

			// Fill 'er up
			raf.seek(2 * Pattern.MAX_HASH + m_offsets[(int)hashcode] * 2);
			for (int c = 0; c < wordIndices.length; c++) {
				wordIndices[c] = raf.readShort();
			}
			raf.close();
			return wordIndices;
		}
		catch (IOException ioe) {
			throw new ScrabaidException ("IOException occurred whilst reading the RandomAccessFile for the pattern collection.");
		}
	}
	public void write() throws ScrabaidException {
		if (m_readOnly) {
			throw new ScrabaidException("PatternCollection must not be opened as read only to allow writing.");
		}
		File file;
		try {
			// Open the old file....
			File oldFile = new File(m_directory, m_name + ".dat");
			FileInputStream oldFileInputStream = new FileInputStream(oldFile);
			DataInputStream olddis = new DataInputStream(new BufferedInputStream(oldFileInputStream));

			// Open the new file...
			File newFile = new File(m_directory, m_name + ".new");
			FileOutputStream newFileOutputStream = new FileOutputStream(newFile);
			DataOutputStream newdos = new DataOutputStream(new BufferedOutputStream(newFileOutputStream));

			// Read the indices of the old file....
			short [] oldIndices = new short[Pattern.MAX_HASH];
			short [] newIndices = new short[Pattern.MAX_HASH];
			for (int c = 0; c < Pattern.MAX_HASH; c++) {
				oldIndices[c] = olddis.readShort();
			}

			int[][] pending = new int[m_pending.size()][];
			for (int c = 0; c < m_pending.size(); c++) {
				int[] iArray = (int[]) m_pending.elementAt(c);
				pending[c] = iArray;
				newIndices[iArray[0]]++;
			}
			m_pending = new Vector();	// Do it now so it can be garbage collected
			Arrays.sort(pending, new IntArrayComparator());

			// Write out the indices.
			for (int c = 0; c < Pattern.MAX_HASH; c++) {
				newdos.writeShort(newIndices[c]+oldIndices[c]);
			}
			int arrayIndex = 0;
			// Now write out the contents....
			for (int c = 0; c < Pattern.MAX_HASH; c++) {
				// Read in the old contents.
				for (int c2 = 0; c2 < oldIndices[c]; c2++) {
					newdos.writeShort(olddis.readShort());
				}
				// Add the new contents
				for (int c2 = 0; c2 < newIndices[c]; c2++) {
					newdos.writeShort( (short) pending[arrayIndex++][1]);
				}
			}

			// Delete the old file...
			olddis.close();
			newdos.close();
			oldFile.delete();

			// Rename the new file...
			newFile.renameTo(oldFile);
		}
		catch (IOException ioe) {
			ioe.printStackTrace();
			System.exit(1);
		}
	}
	class IntArrayComparator implements Comparator {
		public int compare(Object o1, Object o2) {
			int[]a = (int[])o1;
			int[]b = (int[])o2;
			if (a[0] != b[0])
				return (a[0]-b[0]);
			else
				return (a[1]-b[1]);
		}
		public boolean equals(Object o1, Object o2) {
			return (compare(o1,o2) == 0);
		}
	}
}

