/home/wpollock1/public_html/restricted/Java2/SearchEngine/com/wpollock/searchengine/FileListModel.java

package com.wpollock.searchengine;

import static com.wpollock.searchengine.Main.*;
import static com.wpollock.searchengine.MaintenanceWindow.*;

import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.*;

import javax.swing.table.AbstractTableModel;

/** The status of files added to the index.
 *  TODO: Internationalize the strings, just to show how.
 */

enum FileStatus { OKAY("Indexed"),
		MISSING("File not found"),
		NEEDS_UPDATE("File changed since last indexed");
	private String displayText = null;

	private FileStatus ( String text ) { displayText = text; }

	@Override
	public String toString () {
		return displayText;
	}
}
/** This class represents the list of indexed files.  A simple List would work,
 * but using a TableModel allows one to easily display the files in a JTable.
 * (It also shows how to create and use a JTable.)
 *
 * @author Wayne Pollock
 */

class FileListModel extends AbstractTableModel {

	private static final long serialVersionUID = 1L;

	private String[] columnNames = { "File Name", "Status" };
	private static FileListModel model = null;

	static java.util.List<FileItem> fileList = new ArrayList<>();

	FileListModel () {
		model = this;  // Save a reference so others can get at it.

		// The data of the model is read from the saved file at startup
		// TODO: Adjust column widths
	}

	/** This is a Singleton model.
	 * Note this method is not thread-safe.
	 * @return The file list model used by this application.
	 * (This is the list of currently indexed files.)
	 */
	public static FileListModel getModel() {
		if ( model == null )
			model = new FileListModel();
		return model;
	}

	/** Get file name for docID.
	 *
	 * @param docID The ID of the file
	 * @return The canonical absolute pathname for the file.
	 */
	public String getFileName ( long docID ) {
		for ( FileItem file : fileList ) {
			if ( file.fileID == docID )
				return file.fileName;
		}
		return null;  // document not found
	}

	/** Returns a Set of the docIDs of any selected files from the JTable.
	 * @return  The Set of docIDs, which may be empty.
	 */
	Set<Long> getSelectedFiles() {
		Set<Long> selectedFiles = new HashSet<>();
		int [] selectedRows = fileTable.getSelectedRows();
		for ( int row : selectedRows ) {
			selectedFiles.add( fileList.get(row).fileID );
		}
		return selectedFiles;
	}

	/** Adds a new file to the file list (model).  If successful,
	 *  Main.nextID is incremented by one.  (TODO:  Move that to this file?)
	 *
	 * @param fileName  Absolute Pathname of the file to add.
	 * @return the DocID of the newly added file.
	 */
	public long addFile ( String fileName ) {
		long docID = Main.nextID;
		++Main.nextID;
		File file = new File( fileName );
		long modTimeStamp = file.lastModified();
		if ( modTimeStamp == 0L ) // File doesn't exist or can't be opened
			throw new IllegalArgumentException(
					"Can't access file \"" + fileName + "\"." );
		FileItem fi = new FileItem( docID, fileName, modTimeStamp );
		fileList.add(fi);
		fireTableDataChanged();  // Notify JTable to redraw itself

		// TODO:  This should use swingUtilities.invokeAndWait:
		Main.numItemsIndexed.setText( "" + fileList.size() );
		MaintenanceWindow.numItemsIndexed.setText( "" + fileList.size() );
		return docID;
	}

	public void removeFile ( long docID ) {
		// Find FileItem in List with this docID:
		Iterator<FileItem> it = fileList.iterator();
		while ( it.hasNext() ) {
			FileItem file = it.next();
			if ( file.fileID == docID ) {
				it.remove();
				break;
			}
		}
		fireTableDataChanged();  // Notify JTable to redraw itself

		// TODO:  This should use swingUtilities.invokeAndWait:
		Main.numItemsIndexed.setText( "" + fileList.size() );
		MaintenanceWindow.numItemsIndexed.setText( "" + fileList.size() );
	}

	/** Finds the docID of a given filename.  Note, if item isn't found,
	 * the caller will get an exception from auto-unboxing a null.  TODO:
	 * have method return long, and throw exception if name not found.
	 *
	 * @param fileName The name of the file to search for.
	 * @return The docID of the given file, or null if the file isn't found.
	 */
	public Long getDocID ( String fileName ) {
		for ( FileItem file : fileList ) {
			if ( file.fileName.equals(fileName) )
				return new Long(file.fileID);
		}
		return null;  // document not found
	}

	public boolean contains ( String fileName ) {
		for ( FileItem fi : fileList ) {
			if ( fi.fileName.equals(fileName) )
				return true;
		}
		return false;
	}

	/** Finds all document IDs in the index.
	 *
	 * @return Set<Long> of all docIDs
	 */
	public Set<Long> getAllDocIDs () {
		Set<Long> results = new TreeSet<>();
		for ( FileItem file : fileList )
			results.add( file.fileID );
		return results;
	}

	void updateFileModTime ( long docID ) {
		// Find the fileItem with the specified docID:
		Iterator<FileItem> it = fileList.iterator();
		while (it.hasNext() ) {
			FileItem fi = it.next();
			if ( fi.fileID == docID ) {
				fi.modificationTime = new File( fi.fileName ).lastModified();
				break;
			}
		}
		fireTableDataChanged();  // Notify JTable to redraw itself
	}

	void saveIndexToFile () {

		// Determine absolute pathname of file in user's home directory:
		File indexFile = new File( Main.indexFileName );

		// Create and initialize new file if missing:
		if ( ! indexFile.exists() )
			try { indexFile.createNewFile(); }
		    catch ( Exception e ) {  // Can't create UTF-8 file in user's home
			    e.printStackTrace(); // Should never happen
		    }
		PrintWriter file = null;
		try {  // TODO: Convert to Java 7's Try-with-Resources
			file = new PrintWriter( new OutputStreamWriter(
					  new FileOutputStream( indexFile ), FILE_ENCODING ) );

			// Print file header:
			file.println( FILE_HEADER );  // File header and version
			file.println( Main.nextID );  // Next FileID number to use

			// Add file list:
			for ( FileItem fi : fileList ) {
				file.println( fi.fileID + "\t" + fi.fileName
						+ "\t" + fi.modificationTime );
			}
			file.println();  // Add a separator line.

			// Add index data:
			for ( String word : IndexUtils.invertedIndex.keySet() ) {
				Set<DocPos> dataSet = IndexUtils.invertedIndex.get( word );
				file.print( word );
				for ( DocPos dp : dataSet ) {
					file.print( " " + dp.docID + "," + dp.pos );
				}
				file.println();
			}

		file.close();
		} catch (Exception e) {  //
			e.printStackTrace();
			System.exit(0);
		}
	}

	// Remaining methods are standard for any DocumentModels:

	@Override
	public int getColumnCount() {
        return columnNames.length;
    }

	@Override
    public int getRowCount() {
    	return fileList.size(); // data.length;
    }

	@Override
    public String getColumnName(int col) {
        return columnNames[col];
    }

	@Override
	public Object getValueAt(int row, int col) {
		FileItem fileData = fileList.get(row);
		switch ( col ) {
		case 0: return fileData.fileName;
		case 1:  // To determine the status, must check if file exists and
				 // its mod time:
			File file = new File( fileData.fileName );
			FileStatus status = FileStatus.OKAY;
			if ( ! file.exists() ) status = FileStatus.MISSING;
			else if ( file.lastModified() > fileData.modificationTime )
				status = FileStatus.NEEDS_UPDATE;
			return status;
		}
		return null;
    }

	@Override
    public Class<?> getColumnClass ( int col ) {
		return String.class;
    }

	@Override
	public boolean isCellEditable(int row, int col) {
		return false;
	}
}