/*
 * This file is part of NixNote/NeighborNote 
 * Copyright 2009 Randy Baumgarte
 * Copyright 2013 Yuki Takahashi
 * 
 * This file may be licensed under the terms of of the
 * GNU General Public License Version 2 (the ``GPL'').
 *
 * Software distributed under the License is distributed
 * on an ``AS IS'' basis, WITHOUT WARRANTY OF ANY KIND, either
 * express or implied. See the GPL for the specific language
 * governing rights and limitations.
 *
 * You should have received a copy of the GPL along with this
 * program. If not, go to http://www.gnu.org/licenses/gpl.html
 * or write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
*/

package cx.fbn.nevernote.utilities;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;
import java.util.Vector;

import com.evernote.edam.type.LinkedNotebook;
import com.evernote.edam.type.Note;
import com.evernote.edam.type.Notebook;
import com.evernote.edam.type.SavedSearch;
import com.evernote.edam.type.Tag;
import com.trolltech.qt.QThread;
import com.trolltech.qt.core.QDateTime;
import com.trolltech.qt.gui.QImage;
import com.trolltech.qt.gui.QPixmap;
import com.trolltech.qt.sql.QSqlQuery;
import com.trolltech.qt.xml.QDomAttr;
import com.trolltech.qt.xml.QDomDocument;
import com.trolltech.qt.xml.QDomElement;
import com.trolltech.qt.xml.QDomNodeList;

import cx.fbn.nevernote.Global;
import cx.fbn.nevernote.evernote.NoteMetadata;
import cx.fbn.nevernote.filters.EnSearch;
import cx.fbn.nevernote.filters.NotebookCounter;
import cx.fbn.nevernote.filters.TagCounter;
import cx.fbn.nevernote.gui.NoteTableModel;
import cx.fbn.nevernote.signals.NotebookSignal;
import cx.fbn.nevernote.signals.StatusSignal;
import cx.fbn.nevernote.signals.TagSignal;
import cx.fbn.nevernote.signals.ThreadSignal;
import cx.fbn.nevernote.signals.TrashSignal;
import cx.fbn.nevernote.sql.DatabaseConnection;
import cx.fbn.nevernote.threads.CounterRunner;
import cx.fbn.nevernote.threads.SaveRunner;


public class ListManager  {

	
	private final ApplicationLogger logger;  
	DatabaseConnection				conn;
	QSqlQuery						deleteWords;
	QSqlQuery						insertWords;
	
	private List<Tag>				tagIndex;
	private List<Notebook>			notebookIndex;
	private List<Notebook>			archiveNotebookIndex;
	private List<String>			localNotebookIndex;
	private List<LinkedNotebook>	linkedNotebookIndex;

	private List<SavedSearch>		searchIndex;

	private List<String>			selectedNotebooks;
	private final NoteTableModel			noteModel;
	
	
	private List<String>			selectedTags;
	private String					selectedSearch;
	ThreadSignal					signals;
	public StatusSignal				status;
	private final CounterRunner		notebookCounterRunner;
	private final QThread			notebookThread;
	private final CounterRunner		tagCounterRunner;
	private final QThread			tagThread;
	
	private final CounterRunner		trashCounterRunner;
	private final QThread			trashThread;
	public TrashSignal				trashSignal;
	
	private List<NotebookCounter>	notebookCounter;				// count of displayed notes in each notebook
	private List<TagCounter>		tagCounter;						// count of displayed notes for each tag
	
	private EnSearch				enSearch;
	private boolean 				enSearchChanged;
	public HashMap<String, String>	wordMap;
	public TagSignal 				tagSignal;
	public NotebookSignal			notebookSignal;
	public boolean 					refreshCounters;			// Used to control when to recount lists
	private int						trashCount;
    public SaveRunner				saveRunner;					// Thread used to save content.  Used because the xml conversion is slowwwww
    QThread							saveThread;
	
//    private final HashMap<String, QImage> thumbnailList;
    
	// Constructor
 	public ListManager(DatabaseConnection d, ApplicationLogger l) {
 		conn = d;
 		logger = l;
 			
 		conn.getTagTable().cleanupTags();
    	status = new StatusSignal();
		signals = new ThreadSignal();
		
		// setup index locks
		enSearchChanged = false;
		
		// Setup arrays
		noteModel = new NoteTableModel(this);
		selectedTags = new ArrayList<String>();

		notebookCounter = new ArrayList<NotebookCounter>();
		tagCounter = new ArrayList<TagCounter>();
		selectedNotebooks = new ArrayList<String>();
				
		reloadIndexes();
		
 		notebookSignal = new NotebookSignal();
 		notebookCounterRunner = new CounterRunner("notebook_counter.log", CounterRunner.NOTEBOOK, 
 						Global.getDatabaseUrl(), Global.getIndexDatabaseUrl(), Global.getResourceDatabaseUrl(), Global.getBehaviorDatabaseUrl(),
 						Global.getDatabaseUserid(), Global.getDatabaseUserPassword(), Global.cipherPassword);
 		notebookCounterRunner.setNoteIndex(getNoteIndex());
 		notebookCounterRunner.notebookSignal.countsChanged.connect(this, "setNotebookCounter(List)");
		notebookThread = new QThread(notebookCounterRunner, "Notebook Counter Thread");
		notebookThread.start();
		
 		tagSignal = new TagSignal();
 		tagCounterRunner = new CounterRunner("tag_counter.log", CounterRunner.TAG, 
 				Global.getDatabaseUrl(), Global.getIndexDatabaseUrl(), Global.getResourceDatabaseUrl(), Global.getBehaviorDatabaseUrl(),
 				Global.getDatabaseUserid(), Global.getDatabaseUserPassword(), Global.cipherPassword);
 		tagCounterRunner.setNoteIndex(getNoteIndex());
 		tagCounterRunner.tagSignal.countsChanged.connect(this, "setTagCounter(List)");
		tagThread = new QThread(tagCounterRunner, "Tag Counter Thread");
		tagThread.start();
		
 		trashSignal = new TrashSignal();
 		trashCounterRunner = new CounterRunner("trash_counter.log", CounterRunner.TRASH, 
 				Global.getDatabaseUrl(), Global.getIndexDatabaseUrl(), Global.getResourceDatabaseUrl(), Global.getBehaviorDatabaseUrl(),
 				Global.getDatabaseUserid(), Global.getDatabaseUserPassword(), Global.cipherPassword);
 		trashCounterRunner.trashSignal.countChanged.connect(this, "trashSignalReceiver(Integer)");
		trashThread = new QThread(trashCounterRunner, "Trash Counter Thread");
		trashThread.start();
// 		reloadTrashCount();
 		
		wordMap = new HashMap<String, String>();
		tagSignal = new TagSignal();
		
		logger.log(logger.EXTREME, "Setting save thread");
		saveRunner = new SaveRunner("saveRunner.log", 
				Global.getDatabaseUrl(), Global.getIndexDatabaseUrl(), Global.getResourceDatabaseUrl(), Global.getBehaviorDatabaseUrl(),
				Global.getDatabaseUserid(), Global.getDatabaseUserPassword(), Global.cipherPassword);
		saveThread = new QThread(saveRunner, "Save Runner Thread");
		saveThread.start();
		
//		thumbnailList = conn.getNoteTable().getThumbnails();
//		thumbnailList = new HashMap<String,QImage>();
		
		linkedNotebookIndex = conn.getLinkedNotebookTable().getAll();
		loadNoteTitleColors();
		refreshCounters = true;
		refreshCounters();
				
	}
 	
 	public void stop() {
 		saveRunner.addWork("stop", "");
 		tagCounterRunner.release(CounterRunner.EXIT);
 		notebookCounterRunner.release(CounterRunner.EXIT);
 		trashCounterRunner.release(CounterRunner.EXIT);
 		
 		logger.log(logger.MEDIUM, "Waiting for notebookCounterThread to stop"); 			
		try {
			notebookThread.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
 		}
		
 		logger.log(logger.MEDIUM, "Waiting for tagCounterThread to stop"); 			
		try {
			tagThread.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
 		}

 		logger.log(logger.MEDIUM, "Waiting for trashThread to stop"); 			
		try {
			trashThread.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
 		}


		logger.log(logger.MEDIUM, "Waiting for saveThread to stop"); 			
		try {
			saveThread.join(0);
		} catch (InterruptedException e) {
			e.printStackTrace();
 		}

 	}

 	//***************************************************************
 	//***************************************************************
 	//* Refresh lists after a db sync
 	//***************************************************************
 	//***************************************************************
 	public void saveUpdatedNotes(HashMap<Integer, Note> notes, HashMap<Integer, String> contents) {
 		for (int i = 0; i < notes.size(); i++) {
 			Note n = notes.get(i);
 			String content = contents.get(i);
 			saveRunner.addWork(n.getGuid(), content);
 			conn.getNoteTable().updateNoteTitle(n.getGuid(), n.getTitle());
 		}
 	}
 	
 	public void refreshLists() {
 		setSavedSearchIndex(conn.getSavedSearchTable().getAll());
 		setTagIndex(conn.getTagTable().getAll());
 		setNotebookIndex(conn.getNotebookTable().getAll());
 		
 		List<Notebook> local = conn.getNotebookTable().getAllLocal();
 		localNotebookIndex = new ArrayList<String>();
 		for (int i=0; i<local.size(); i++)
 			localNotebookIndex.add(local.get(i).getGuid());
 		
		noteModel.setMasterNoteIndex(conn.getNoteTable().getAllNotes());
		// For performance reasons, we didn't get the tags for every note individually.  We now need to 
		// get them
		List<cx.fbn.nevernote.sql.NoteTagsRecord> noteTags = conn.getNoteTable().noteTagsTable.getAllNoteTags();
 		for (int i=0; i<getMasterNoteIndex().size(); i++) {
			List<String> tags = new ArrayList<String>();
			List<String> names = new ArrayList<String>();
			for (int j=0; j<noteTags.size(); j++) {
				if (getMasterNoteIndex().get(i).getGuid().equals(noteTags.get(j).noteGuid)) {
					tags.add(noteTags.get(j).tagGuid);
					names.add(getTagNameByGuid(noteTags.get(j).tagGuid));
				}
			}
			
			getMasterNoteIndex().get(i).setTagGuids(tags);
			getMasterNoteIndex().get(i).setTagNames(names);
		}
		
		
		//setUnsynchronizedNotes(conn.getNoteTable().getUnsynchronizedGUIDs());
		
		linkedNotebookIndex = conn.getLinkedNotebookTable().getAll();
		
		enSearchChanged = true;
 	}

 	public void reloadTagIndex() {
 		setTagIndex(conn.getTagTable().getAll());	
 	}
 	public void reloadIndexes() {
		//setUnsynchronizedNotes(conn.getNoteTable().getUnsynchronizedGUIDs());

 		List<Notebook> local = conn.getNotebookTable().getAllLocal();
 		localNotebookIndex = new ArrayList<String>();
 		for (int i=0; i<local.size(); i++)
 			localNotebookIndex.add(local.get(i).getGuid());
 		
 		reloadTagIndex();
		// Load notebooks
		setNotebookIndex(conn.getNotebookTable().getAll());
		// load archived notebooks (if note using the EN interface)
		setArchiveNotebookIndex(conn.getNotebookTable().getAllArchived());
		// load saved search index
		setSavedSearchIndex(conn.getSavedSearchTable().getAll());
		// Load search helper utility
		enSearch = new EnSearch(conn,  logger, "", getTagIndex(), Global.getRecognitionWeight());
		logger.log(logger.HIGH, "Building note index");

//		if (getMasterNoteIndex() == null) { 
			noteModel.setMasterNoteIndex(conn.getNoteTable().getAllNotes());
//		}
		// For performance reasons, we didn't get the tags for every note individually.  We now need to 
		// get them
		List<cx.fbn.nevernote.sql.NoteTagsRecord> noteTags = conn.getNoteTable().noteTagsTable.getAllNoteTags();
 		for (int i=0; i<getMasterNoteIndex().size(); i++) {
			List<String> tags = new ArrayList<String>();
			List<String> names = new ArrayList<String>();
			for (int j=0; j<noteTags.size(); j++) {
				if (getMasterNoteIndex().get(i).getGuid().equals(noteTags.get(j).noteGuid)) {
					tags.add(noteTags.get(j).tagGuid);
					names.add(getTagNameByGuid(noteTags.get(j).tagGuid));
				}
			}
			
			getMasterNoteIndex().get(i).setTagGuids(tags);
			getMasterNoteIndex().get(i).setTagNames(names);
		}
 		
 		setNoteIndex(getMasterNoteIndex());

 	}
 	
	//***************************************************************
	//***************************************************************
	//* selected notebooks
	//***************************************************************
	//***************************************************************
 	// Return the selected notebook(s)
	public List<String> getSelectedNotebooks() {
		return selectedNotebooks;
	}
	// Set the current selected notebook(s)
	public void setSelectedNotebooks(List <String> s) {
		if (s == null) 
			s = new ArrayList<String>();
		selectedNotebooks = s;
	}
	
				
    //***************************************************************
    //***************************************************************
    //** These functions deal with setting & retrieving the master lists
    //***************************************************************
    //***************************************************************
	// Get the note table model
	public NoteTableModel getNoteTableModel() {
		return noteModel;
	}
	// save the saved search index
	private void setSavedSearchIndex(List<SavedSearch> t) {
		searchIndex = t;
	}
	// Retrieve the Tag index
	public List<SavedSearch> getSavedSearchIndex() {
		return searchIndex;

	}
	// save the tag index
	private void setTagIndex(List<Tag> t) {
		tagIndex = t;
	}	
	// Retrieve the Tag index
	public List<Tag> getTagIndex() {
		return tagIndex;
	}
	private void setNotebookIndex(List<Notebook> t) {
		notebookIndex = t;
	}
	private void setArchiveNotebookIndex(List<Notebook> t) {
		archiveNotebookIndex = t;
	}
	// Retrieve the Notebook index
	public List<Notebook> getNotebookIndex() {
		return notebookIndex;

	}
	public List<LinkedNotebook> getLinkedNotebookIndex() {
		return linkedNotebookIndex;
	}
	public List<Notebook> getArchiveNotebookIndex() {
		return archiveNotebookIndex;
	}
	// Save the current note list
	private void setNoteIndex(List<Note> n) {
		noteModel.setNoteIndex(n);
		refreshNoteMetadata();
	}
	public void refreshNoteMetadata() {
		noteModel.setNoteMetadata(conn.getNoteTable().getNotesMetaInformation());
	}
	// Update a note's meta data
	public void updateNoteMetadata(NoteMetadata meta) {
		noteModel.metaData.remove(meta);
		noteModel.metaData.put(meta.getGuid(), meta);
		conn.getNoteTable().updateNoteMetadata(meta);
	}
	// Get the note index
	public synchronized List<Note> getNoteIndex() {
		return noteModel.getNoteIndex();
	}
	// Save the count of notes per notebook
	public void setNotebookCounter(List<NotebookCounter> n) {
		notebookCounter = n;
		notebookSignal.refreshNotebookTreeCounts.emit(getNotebookIndex(), notebookCounter);
	}
	public List<NotebookCounter> getNotebookCounter() {
		return notebookCounter;
	}
	// Save the count of notes for each tag
	public void setTagCounter(List<TagCounter> n) {
		tagCounter = n;
		tagSignal.refreshTagTreeCounts.emit(tagCounter);
	}
	public List<TagCounter> getTagCounter() {
		return tagCounter;
	}
	public List<String> getLocalNotebooks() {
		return localNotebookIndex;
	}

//	public void setUnsynchronizedNotes(List<String> l) {
//		noteModel.setUnsynchronizedNotes(l);
//	}
	// Return a count of items in the trash
	public int getTrashCount() {
		return trashCount;
	}
	// get the EnSearch variable
	public EnSearch getEnSearch() {
		return enSearch;
	}
	public List<Note> getMasterNoteIndex() {
		return noteModel.getMasterNoteIndex();
	}
	// Thumbnails
//	public HashMap<String, QImage> getThumbnails() {
//		return thumbnailList;
//	}
	public HashMap<String, NoteMetadata> getNoteMetadata() {
		return noteModel.metaData;
	}
	public QImage getThumbnail(String guid) {
//		if (getThumbnails().containsKey(guid))
//			return getThumbnails().get(guid);
		
		QImage img = new QImage();
		img = QImage.fromData(conn.getNoteTable().getThumbnail(guid));
		if (img == null || img.isNull()) 
			return null;
		//getThumbnails().put(guid, img);
		return img;
	}
	public QPixmap getThumbnailPixmap(String guid) {
//		if (getThumbnails().containsKey(guid))
//			return getThumbnails().get(guid);
		
		QPixmap img = new QPixmap();
		img.loadFromData(conn.getNoteTable().getThumbnail(guid));
		if (img == null || img.isNull()) 
			return null;
		//getThumbnails().put(guid, img);
		return img;
	}
    //***************************************************************
    //***************************************************************
    //** These functions deal with setting & retrieving filters
    //***************************************************************
    //***************************************************************
	public void setEnSearch(String t) {
		enSearch = new EnSearch(conn,logger, t, getTagIndex(), Global.getRecognitionWeight());
		enSearchChanged = true;
	}
	// Save search tags
	public void setSelectedTags(List<String> selectedTags) {
		this.selectedTags = selectedTags;
	}
	// Save seleceted search
	public void setSelectedSavedSearch(String s) {
		this.selectedSearch = s;
	}
	// Get search tags
	public List<String> getSelectedTags() {
		return selectedTags;
	}
	// Get saved search
	public String getSelectedSearch() {
		return selectedSearch;
	}
	
	
	
	
    //***************************************************************
    //***************************************************************
    //** Note functions
    //***************************************************************
    //***************************************************************
	// Save Note Tags
	public void saveNoteTags(String noteGuid, List<String> tags, boolean isDirty) {
		logger.log(logger.HIGH, "Entering ListManager.saveNoteTags");
		// 同じタグが付けられた履歴を記録（必ずdeleteNoteTagの前にやる）
		for (int i = 0; i < tags.size(); i++) {
			String tagName = tags.get(i);
			for (int j = 0; j < tagIndex.size(); j++) {
				if (tagIndex.get(j).getName().equalsIgnoreCase(tagName)) {
					conn.getHistoryTable().addSameTagHistory(noteGuid, tagIndex.get(j).getGuid());
				}
			}
		}
		
		String tagName;
		conn.getNoteTable().noteTagsTable.deleteNoteTag(noteGuid);
		List<String> tagGuids = new ArrayList<String>();
		boolean newTagCreated = false;
		
		for (int i=0; i<tags.size(); i++) {
			tagName = tags.get(i);
			boolean found = false;
			for (int j=0; j<tagIndex.size(); j++) {
				if (tagIndex.get(j).getName().equalsIgnoreCase(tagName)) {
					conn.getNoteTable().noteTagsTable.saveNoteTag(noteGuid, tagIndex.get(j).getGuid(), isDirty);
					tagGuids.add(tagIndex.get(j).getGuid());
					j=tagIndex.size()+1;
					found = true;
				}
			}
			if (!found) {
				Tag nTag = new Tag();
				nTag.setName(tagName);
				Calendar currentTime = new GregorianCalendar();
				Long l = new Long(currentTime.getTimeInMillis());
				long prevTime = l;
				while (l==prevTime) {
					currentTime = new GregorianCalendar();
					l=currentTime.getTimeInMillis();
				}
				String randint = new String(Long.toString(l));
			
				nTag.setUpdateSequenceNum(0);
				nTag.setGuid(randint);
				conn.getTagTable().addTag(nTag, true);
				getTagIndex().add(nTag);
				conn.getNoteTable().noteTagsTable.saveNoteTag(noteGuid, nTag.getGuid(), isDirty);
				tagGuids.add(nTag.getGuid());
				newTagCreated = true;
			}
		}
		
		for (int i=0; i<getNoteIndex().size(); i++) {
			if (getNoteIndex().get(i).getGuid().equals(noteGuid)) {
				getNoteIndex().get(i).setTagNames(tags);
				getNoteIndex().get(i).setTagGuids(tagGuids);
				i=getNoteIndex().size()+1;
			}
		}
		if (newTagCreated)
			tagSignal.listChanged.emit();
		logger.log(logger.HIGH, "Leaving ListManager.saveNoteTags");
	}
	// Delete a note
	public void deleteNote(String guid) {
		trashCounterRunner.abortCount = true;
		Calendar currentTime = new GregorianCalendar();
		Long l = new Long(currentTime.getTimeInMillis());
		long prevTime = l;
		while (l==prevTime) {
			currentTime = new GregorianCalendar();
			l=currentTime.getTimeInMillis();
		}
		
		for (int i=0; i<getMasterNoteIndex().size(); i++) {
			if (getMasterNoteIndex().get(i).getGuid().equals(guid)) {
				getMasterNoteIndex().get(i).setActive(false);
				getMasterNoteIndex().get(i).setDeleted(l);
				i=getMasterNoteIndex().size();
			}
		}
		for (int i=0; i<getNoteIndex().size(); i++) {
			if (getNoteIndex().get(i).getGuid().equals(guid)) {
				getNoteIndex().get(i).setActive(false);
				getNoteIndex().get(i).setDeleted(l);
				i=getNoteIndex().size();
			}
		}
		conn.getNoteTable().deleteNote(guid);
		reloadTrashCount();
	}
	// Delete a note
	public void restoreNote(String guid) {
		trashCounterRunner.abortCount = true;
		for (int i=0; i<getMasterNoteIndex().size(); i++) {
			if (getMasterNoteIndex().get(i).getGuid().equals(guid)) {
				getMasterNoteIndex().get(i).setActive(true);
				getMasterNoteIndex().get(i).setDeleted(0);
				i=getMasterNoteIndex().size();
			}
		}
		for (int i=0; i<getNoteIndex().size(); i++) {
			if (getNoteIndex().get(i).getGuid().equals(guid)) {
				getNoteIndex().get(i).setActive(true);
				getNoteIndex().get(i).setDeleted(0);
				i=getNoteIndex().size();
			}
		}
		conn.getNoteTable().restoreNote(guid);
		reloadTrashCount();
	}
	public void updateNote(Note n) {
		
		for (int i=0; i<getMasterNoteIndex().size(); i++) {
			if (getMasterNoteIndex().get(i).getGuid().equals(n.getGuid())) {
				getMasterNoteIndex().remove(i);
				getMasterNoteIndex().add(n);
			}
		}
		for (int i=0; i<getNoteIndex().size(); i++) {
			if (getNoteIndex().get(i).getGuid().equals(n.getGuid())) {
				getNoteIndex().get(i).setActive(true);
				getNoteIndex().get(i).setDeleted(0);
				i=getNoteIndex().size();
			}
		}
		conn.getNoteTable().updateNote(n);
	}
	// Add a note.  
	public void addNote(Note n, NoteMetadata meta) {
		noteModel.addNote(n, meta);
		noteModel.metaData.put(n.getGuid(), meta);
	}
	// Expunge a note
	public void expungeNote(String guid) {
		trashCounterRunner.abortCount = true;
		for (int i=0; i<getMasterNoteIndex().size(); i++) {
			if (getMasterNoteIndex().get(i).getGuid().equals(guid)) {
				getMasterNoteIndex().remove(i);
				i=getMasterNoteIndex().size();
			}
		}
		for (int i=0; i<getNoteIndex().size(); i++) {
			if (getNoteIndex().get(i).getGuid().equals(guid)) {
				getNoteIndex().remove(i);
				i=getNoteIndex().size();
			}
		}
		conn.getNoteTable().expungeNote(guid, false, true);
		reloadTrashCount();
	}
	// Expunge a note
	public void emptyTrash() {
		trashCounterRunner.abortCount = true;		
		for (int i=getMasterNoteIndex().size()-1; i>=0; i--) {
			if (!getMasterNoteIndex().get(i).isActive()) {
				getMasterNoteIndex().remove(i);
			}
		}
		
		for (int i=getNoteIndex().size()-1; i>=0; i--) {
			if (!getNoteIndex().get(i).isActive()) {
				getNoteIndex().remove(i);
			} 
		}

		conn.getNoteTable().expungeAllDeletedNotes();
		reloadTrashCount();
	}
	// The trash counter thread has produced a result
	@SuppressWarnings("unused")
	private void trashSignalReceiver(Integer i) {
		trashCount = i;
		trashSignal.countChanged.emit(i);
	}
	// Update note contents
	public void updateNoteContent(String guid, String content) {
		logger.log(logger.HIGH, "Entering ListManager.updateNoteContent");
//		EnmlConverter enml = new EnmlConverter(logger);
//		String text = enml.convert(guid, content);
		
		// Update the list tables 
/*		for (int i=0; i<masterNoteIndex.size(); i++) {
			if (masterNoteIndex.get(i).getGuid().equals(guid)) {
				masterNoteIndex.get(i).setContent(text);
				i = masterNoteIndex.size();
			}
		}
		// Update the list tables 
		for (int i=0; i<getNoteIndex().size(); i++) {
			if (getNoteIndex().get(i).getGuid().equals(guid)) {
				getNoteIndex().get(i).setContent(text);
				i = getNoteIndex().size();
			}
		}
*/		
		// Check if any new tags were encountered
/*		if (enml.saveInvalidXML) {
			List<String> elements = Global.invalidElements;
			for (int i=0; i<elements.size(); i++) {
				conn.getInvalidXMLTable().addInvalidElement(elements.get(i));
			}
			for (String key : Global.invalidAttributes.keySet()) {
				ArrayList<String> attributes = Global.invalidAttributes.get(key);
				for (int i=0; i<attributes.size(); i++) {
					conn.getInvalidXMLTable().addInvalidAttribute(key, attributes.get(i));
				}
			}
		}
*/
		saveRunner.addWork(guid, content);
//		conn.getNoteTable().updateNoteContent(guid, content);
		logger.log(logger.HIGH, "Leaving ListManager.updateNoteContent");
	}
	// Update a note creation date
	public void updateNoteCreatedDate(String guid, QDateTime date) {
		noteModel.updateNoteCreatedDate(guid, date);
		conn.getNoteTable().updateNoteCreatedDate(guid, date);
	}
	// Subject date has been changed
	public void updateNoteSubjectDate(String guid, QDateTime date) {
		noteModel.updateNoteSubjectDate(guid, date);
		conn.getNoteTable().updateNoteSubjectDate(guid, date);
	}
	// Author has changed
	public void updateNoteAuthor(String guid, String author) {
		noteModel.updateNoteAuthor(guid, author);
		conn.getNoteTable().updateNoteAuthor(guid, author);
	}
	// Author has changed
	public void updateNoteGeoTag(String guid, Double lon, Double lat, Double alt) {
		for (int i=0; i<getMasterNoteIndex().size(); i++) {
			if (getMasterNoteIndex().get(i).getGuid().equals(guid)) {
				getMasterNoteIndex().get(i).getAttributes().setLongitude(lon);
				getMasterNoteIndex().get(i).getAttributes().setLongitudeIsSet(true);
				getMasterNoteIndex().get(i).getAttributes().setLatitude(lat);
				getMasterNoteIndex().get(i).getAttributes().setLatitudeIsSet(true);
				getMasterNoteIndex().get(i).getAttributes().setAltitude(alt);
				getMasterNoteIndex().get(i).getAttributes().setAltitudeIsSet(true);
				i = getMasterNoteIndex().size();
			}	
		}
		// Update the list tables 
		for (int i=0; i<getNoteIndex().size(); i++) {
			if (getNoteIndex().get(i).getGuid().equals(guid)) {
				getNoteIndex().get(i).getAttributes().setLongitude(lon);
				getNoteIndex().get(i).getAttributes().setLongitudeIsSet(true);
				getNoteIndex().get(i).getAttributes().setLatitude(lat);
				getNoteIndex().get(i).getAttributes().setLatitudeIsSet(true);
				getNoteIndex().get(i).getAttributes().setAltitude(alt);
				getNoteIndex().get(i).getAttributes().setAltitudeIsSet(true);
				i = getNoteIndex().size();
			}
		}
		conn.getNoteTable().updateNoteGeoTags(guid, lon, lat, alt);
	}
	// Source URL changed
	public void updateNoteSourceUrl(String guid, String url) {
		noteModel.updateNoteSourceUrl(guid, url);
		conn.getNoteTable().updateNoteSourceUrl(guid, url);
	}
	// Update a note last changed date
	public void updateNoteAlteredDate(String guid, QDateTime date) {
		noteModel.updateNoteChangedDate(guid, date);
		conn.getNoteTable().updateNoteAlteredDate(guid, date);
	}
	// Update a note title
	public void updateNoteTitle(String guid, String title) {
		logger.log(logger.HIGH, "Entering ListManager.updateNoteTitle");
		conn.getNoteTable().updateNoteTitle(guid, title);
		noteModel.updateNoteTitle(guid, title);
		logger.log(logger.HIGH, "Leaving ListManager.updateNoteTitle");
	}
	// Update a note's notebook
	public void updateNoteNotebook(String guid, String notebookGuid) {
		logger.log(logger.HIGH, "Entering ListManager.updateNoteNotebook");
		noteModel.updateNoteNotebook(guid, notebookGuid);
		conn.getNoteTable().updateNoteNotebook(guid, notebookGuid, true);
		logger.log(logger.HIGH, "Leaving ListManager.updateNoteNotebook");
	}
	// Update a note sequence number
	public void updateNoteSequence(String guid, int sequence) {
		logger.log(logger.HIGH, "Entering ListManager.updateNoteSequence");

		conn.getNoteTable().updateNoteSequence(guid, sequence);
		
		for (int i=0; i<noteModel.getMasterNoteIndex().size(); i++) {
			if (noteModel.getMasterNoteIndex().get(i).getGuid().equals(guid)) {
				noteModel.getMasterNoteIndex().get(i).setUpdateSequenceNum(sequence);
				i=noteModel.getMasterNoteIndex().size()+1;
			}
		}
		
		for (int i=0; i<getNoteIndex().size(); i++) {
			if (getNoteIndex().get(i).getGuid().equals(guid)) {
				getNoteIndex().get(i).setUpdateSequenceNum(sequence);
				i=getNoteIndex().size()+1;
			}
		}
		logger.log(logger.HIGH, "Leaving ListManager.updateNoteSequence");
	}
	public void updateNoteGuid(String oldGuid, String newGuid, boolean updateDatabase) {
		logger.log(logger.HIGH, "Entering ListManager.updateNoteGuid");
		if (updateDatabase) 
			conn.getNoteTable().updateNoteGuid(oldGuid, newGuid);
		noteModel.updateNoteGuid(oldGuid, newGuid);
		logger.log(logger.HIGH, "Leaving ListManager.updateNoteGuid");

	}

	
	//************************************************************************************
	//************************************************************************************
	//**  Tag functions
	//************************************************************************************
	//************************************************************************************	
	// Update a tag sequence number
	public void updateTagSequence(String guid, int sequence) {
		logger.log(logger.HIGH, "Entering ListManager.updateTagSequence");

		conn.getTagTable().updateTagSequence(guid, sequence);	
		for (int i=0; i<tagIndex.size(); i++) {
			if (tagIndex.get(i).getGuid().equals(guid)) {
				getTagIndex().get(i).setUpdateSequenceNum(sequence);
				i=tagIndex.size()+1;
			}
		}
		logger.log(logger.HIGH, "Leaving ListManager.updateTagSequence");
	}
	// Update a tag guid number
	public void updateTagGuid(String oldGuid, String newGuid) {
		logger.log(logger.HIGH, "Entering ListManager.updateTagGuid");

		conn.getTagTable().updateTagGuid(oldGuid, newGuid);	
		for (int i=0; i<tagIndex.size(); i++) {
			if (tagIndex.get(i).getGuid().equals(oldGuid)) {
				tagIndex.get(i).setGuid(newGuid);
				i=tagIndex.size()+1;
			}
		}
		logger.log(logger.HIGH, "Leaving ListManager.updateTagGuid");

	}
	// Find all children for a tag
	public List<Tag> findAllChildren(String guid) {
		List<Tag> tags = new ArrayList<Tag>();
		return findAllChildrenRecursive(guid, tags);
	}
	public List<Tag> findAllChildrenRecursive(String guid, List<Tag> tags) {
		
		// Start looping through the tags.  If we find a tag which has a parent that
		// matches guid, then we add it to the list of tags & search for its children.
		for (int i=0; i<getTagIndex().size(); i++) {
			if (getTagIndex().get(i).getParentGuid() != null && getTagIndex().get(i).getParentGuid().equals(guid)) {
				tags.add(getTagIndex().get(i));
				tags = findAllChildrenRecursive(getTagIndex().get(i).getGuid(), tags);
			}
		}
		return tags;
	}
	// Give a list of tags, does any of them match a child tag?
	public boolean checkNoteForChildTags(String guid, List<String> noteTags) {
		boolean returnValue = false;
		List<Tag> children = findAllChildren(guid);
		for (int i=0; i<noteTags.size(); i++) {
			String noteTag = noteTags.get(i);
			for (int j=0; j<children.size(); j++) {
				if (noteTag.equals(children.get(j).getGuid()))
					return true;
			}
		}
		return returnValue;
	}


	//************************************************************************************
	//************************************************************************************
	//**  Notebook functions
	//************************************************************************************
	//************************************************************************************	
	// Delete a notebook
	public void deleteNotebook(String guid) {
		for (int i=0; i<getNotebookIndex().size(); i++) {
			if (getNotebookIndex().get(i).getGuid().equals(guid)) {
				getNotebookIndex().remove(i);
				i=getMasterNoteIndex().size();
			}
		}
		conn.getNotebookTable().expungeNotebook(guid, true);		
	}
	// Rename a stack
	public void renameStack(String oldName, String newName) {
		for (int i=0; i<getNotebookIndex().size(); i++) {
			if (getNotebookIndex().get(i).getStack() != null && 
					getNotebookIndex().get(i).getStack().equalsIgnoreCase(oldName)) {
				getNotebookIndex().get(i).setStack(newName);
			}
		}	
	}
	// Update a notebook sequence number
	public void updateNotebookSequence(String guid, int sequence) {
		logger.log(logger.HIGH, "Entering ListManager.updateNotebookSequence");

		conn.getNotebookTable().updateNotebookSequence(guid, sequence);
		
		for (int i=0; i<notebookIndex.size(); i++) {
			if (notebookIndex.get(i).getGuid().equals(guid)) {
				notebookIndex.get(i).setUpdateSequenceNum(sequence);
				i=notebookIndex.size()+1;
			}
		}
		logger.log(logger.HIGH, "Leaving ListManager.updateNotebookSequence");

	}
	// Update a notebook Guid number
	public void updateNotebookGuid(String oldGuid, String newGuid) {
		logger.log(logger.HIGH, "Entering ListManager.updateNotebookGuid");

		conn.getNotebookTable().updateNotebookGuid(oldGuid, newGuid);
		
		for (int i=0; i<notebookIndex.size(); i++) {
			if (notebookIndex.get(i).getGuid().equals(oldGuid)) {
				notebookIndex.get(i).setGuid(newGuid);
				i=notebookIndex.size()+1;
			}
		}
		logger.log(logger.HIGH, "Leaving ListManager.updateNotebookGuid");

	}
	// Update a notebook Guid number
	public void updateNotebookStack(String oldGuid, String stack) {
		logger.log(logger.HIGH, "Entering ListManager.updateNotebookGuid");

		conn.getNotebookTable().setStack(oldGuid, stack);
		
		for (int i=0; i<notebookIndex.size(); i++) {
			if (notebookIndex.get(i).getGuid().equals(oldGuid)) {
				notebookIndex.get(i).setStack(stack);
				i=notebookIndex.size()+1;
			}
		}
		logger.log(logger.HIGH, "Leaving ListManager.updateNotebookGuid");

	}
	
	
	//************************************************************************************
	//************************************************************************************
	//**  Load and filter the note index
	//************************************************************************************
	//************************************************************************************
	
	public void noteDownloaded(Note n) {
		boolean found = false;
		for (int i=0; i<getMasterNoteIndex().size(); i++) {
			if (getMasterNoteIndex().get(i).getGuid().equals(n.getGuid())) {
				getMasterNoteIndex().set(i,n);
				found = true;
				i=getMasterNoteIndex().size();
			}
		}
		
		if (!found)
			getMasterNoteIndex().add(n);
		
		for (int i=0; i<getNoteIndex().size(); i++) {
			if (getNoteIndex().get(i).getGuid().equals(n.getGuid())) {
				if (filterRecord(getNoteIndex().get(i)))
					getNoteIndex().add(n);
				getNoteIndex().remove(i);
				i=getNoteIndex().size();
			}
		}
		
		if (filterRecord(n))
			getNoteIndex().add(n);
		
	}
	// Check if a note matches the currently selected notebooks, tags, or attribute searches.
	public boolean filterRecord(Note n) {
				
		boolean goodNotebook = false;
		boolean goodTag = false;
		boolean goodStatus = false;
			
		// Check note status
		if (!n.isActive() && Global.showDeleted)
			return true;
		else {
			if (n.isActive() && !Global.showDeleted)
				goodStatus = true;
		}
		
		// Begin filtering results
		if (goodStatus)
			goodNotebook = filterByNotebook(n.getNotebookGuid());
		if (goodNotebook) 
			goodTag = filterByTag(n.getTagGuids());
		if (goodTag) {
			boolean goodCreatedBefore = false;
			boolean goodCreatedSince = false;
			boolean goodChangedBefore = false;
			boolean goodChangedSince = false;
			boolean goodContains = false;
			if (!Global.createdBeforeFilter.hasSelection())
				goodCreatedBefore = true;
			else
				goodCreatedBefore = Global.createdBeforeFilter.check(n);
				
			if (!Global.createdSinceFilter.hasSelection())
				goodCreatedSince = true;
			else
				goodCreatedSince = Global.createdSinceFilter.check(n);
				
			if (!Global.changedBeforeFilter.hasSelection())
				goodChangedBefore = true;
			else
				goodChangedBefore = Global.changedBeforeFilter.check(n);
				if (!Global.changedSinceFilter.hasSelection())
				goodChangedSince = true;
			else
				goodChangedSince = Global.changedSinceFilter.check(n);
			if (!Global.containsFilter.hasSelection())
				goodContains = true;
			else
				goodContains = Global.containsFilter.check(conn.getNoteTable(), n);
				
			if (goodCreatedSince && goodCreatedBefore && goodChangedSince && goodChangedBefore && goodContains)
				return true;
		}	
		return false;
	}
	
	// Trigger a recount of counters
	public void refreshCounters() {
//		refreshCounters= false;
		if (!refreshCounters)
			return;
		refreshCounters = false;
		tagCounterRunner.abortCount = true;
		notebookCounterRunner.abortCount = true;
		trashCounterRunner.abortCount = true;
		countNotebookResults(getNoteIndex());
		countTagResults(getNoteIndex());
		reloadTrashCount();

	}
	// Load the note index based upon what the user wants.
	public void loadNotesIndex() {
		logger.log(logger.EXTREME, "Entering ListManager.loadNotesIndex()");
		
		List<Note> matches;
		if (enSearchChanged || getMasterNoteIndex() == null)
			matches = enSearch.matchWords();
		else
			matches = getMasterNoteIndex();
		
		if (matches == null)
			matches = getMasterNoteIndex();
		
		setNoteIndex(new ArrayList<Note>());
		for (int i=0; i<matches.size(); i++) {
			if (filterRecord(matches.get(i)))
				getNoteIndex().add(matches.get(i));
		}
		refreshCounters = true;
		enSearchChanged = false;
		logger.log(logger.EXTREME, "Leaving ListManager.loadNotesIndex()");
	}
	public void countNotebookResults(List<Note> index) {
		logger.log(logger.EXTREME, "Entering ListManager.countNotebookResults()");
		notebookCounterRunner.abortCount = true;
		if (!Global.mimicEvernoteInterface) 
			notebookCounterRunner.setNoteIndex(index);
		else 
			notebookCounterRunner.setNoteIndex(getMasterNoteIndex());
		notebookCounterRunner.release(CounterRunner.NOTEBOOK);
		logger.log(logger.EXTREME, "Leaving ListManager.countNotebookResults()");
	}
	public void countTagResults(List<Note> index) {
		logger.log(logger.EXTREME, "Entering ListManager.countTagResults");
		trashCounterRunner.abortCount = true;
		if (!Global.tagBehavior().equalsIgnoreCase("DoNothing")) 
			tagCounterRunner.setNoteIndex(index);
		else
			tagCounterRunner.setNoteIndex(getMasterNoteIndex());
		tagCounterRunner.release(CounterRunner.TAG);
		logger.log(logger.EXTREME, "Leaving ListManager.countTagResults()");
	}
	// Update the count of items in the trash
	public void reloadTrashCount() {
		logger.log(logger.EXTREME, "Entering ListManager.reloadTrashCount");
		trashCounterRunner.abortCount = true;
		trashCounterRunner.setNoteIndex(getMasterNoteIndex());
		trashCounterRunner.release(CounterRunner.TRASH);
		logger.log(logger.EXTREME, "Leaving ListManager.reloadTrashCount");
	}	
	
	private boolean filterByNotebook(String guid) {
		boolean good = false;
		if (selectedNotebooks.size() == 0)
			good = true;
		if (!good && selectedNotebooks.contains(guid)) 
			good = true;

		for (int i=0; i<getArchiveNotebookIndex().size() && good; i++) {
			if (guid.equals(getArchiveNotebookIndex().get(i).getGuid())) {
				good = false;
				return good;
			}
		}
		return good;
	}
	private boolean filterByTag(List<String> noteTags) {
		// If either the note has no tags or there are
		// no selected tags, then any note is good.
		if (noteTags == null || selectedTags == null)
			return true;
		
		// If there are no tags selected, then any note  is good
		if (selectedTags.size() == 0) 
			return true;
		
		// If ALL tags must be matched, then check ALL note tags, 
		// otherwise we match on any criteria.
		if (!Global.anyTagSelectionMatch()) {
			for (int i=0; i<selectedTags.size(); i++) {
				String selectedGuid = selectedTags.get(i);
				boolean childMatch = false;
				// If we should include children in the results
				if (Global.includeTagChildren()) {
					childMatch = checkNoteForChildTags(selectedGuid, noteTags);
					// Do we have a match with this tag or any children
					if (!noteTags.contains(selectedGuid)&& !childMatch)
						return false;
				} else {
					// Does this note have a matching tag
					if (!noteTags.contains(selectedGuid))
						return false;
				}
			}
			return true;
		} else {
			// Any match is displayed.
			for (int i=0; i<selectedTags.size(); i++) {
				String selectedGuid = selectedTags.get(i);
				// If we have a simple match, then we're good
				if (noteTags.contains(selectedGuid))
						return true;
				// If we have a match with one of the children tags && we should include child tags
				if (Global.includeTagChildren() && checkNoteForChildTags(selectedGuid, noteTags))
					return true;
			}
			return false;
		}
	}

	public void setNoteSynchronized(String guid, boolean value) {
		getNoteTableModel().updateNoteSyncStatus(guid, value);
	}
	
	public void updateNoteTitleColor(String guid, Integer color) {
		NoteMetadata meta = getNoteMetadata().get(guid);
		if (meta != null) {
			noteModel.updateNoteTitleColor(guid, color);
			meta.setColor(color);
			conn.getNoteTable().updateNoteMetadata(meta);
		}
	}
	public void loadNoteTitleColors() {
		noteModel.setMetaData(getNoteMetadata());
	}
	
	//********************************************************************************
	//********************************************************************************
	//* Support signals from the index thread
	//********************************************************************************
	//********************************************************************************
	// Reset a flag if an index is needed
	public void setIndexNeeded(String guid, String type, Boolean b) {
		if (Global.keepRunning && type.equalsIgnoreCase("content"))
			conn.getNoteTable().setIndexNeeded(guid, false);
		if (Global.keepRunning && type.equalsIgnoreCase("resource")) {
			conn.getNoteTable().noteResourceTable.setIndexNeeded(guid, b);
		}
	}
	
	public boolean threadCheck(int id) {
		if (id == Global.notebookCounterThreadId) 
			return notebookThread.isAlive();
		if (id == Global.tagCounterThreadId) 
			return tagThread.isAlive();
		if (id == Global.trashCounterThreadId) 
			return trashThread.isAlive();
		if (id == Global.saveThreadId) 
			return saveThread.isAlive();
		return false;
	}
	
	
	
	//********************************************************************************
	//********************************************************************************
	//* Utility Functions
	//********************************************************************************
	//********************************************************************************
	public void compactDatabase() {
		conn.compactDatabase();
//		IndexConnection idx = new IndexConnection(logger, "nevernote-compact");
//		idx.dbSetup();
//		idx.dbShutdown();
	}

	// Rebuild the note HTML to something usable
	public List<String> scanNoteForResources(Note n) {
		logger.log(logger.HIGH, "Entering ListManager.scanNoteForResources");
		logger.log(logger.EXTREME, "Note guid: " +n.getGuid());
		QDomDocument doc = new QDomDocument();
		QDomDocument.Result result = doc.setContent(n.getContent());
		if (!result.success) {
			logger.log(logger.MEDIUM, "Parse error when scanning note for resources.");
			logger.log(logger.MEDIUM, "Note guid: " +n.getGuid());
			return null;
		}
				
		List<String> returnArray = new ArrayList<String>();
		QDomNodeList anchors = doc.elementsByTagName("en-media");
		for (int i=0; i<anchors.length(); i++) {
			QDomElement enmedia = anchors.at(i).toElement();
			if (enmedia.hasAttribute("type")) {
				QDomAttr hash = enmedia.attributeNode("hash");
				returnArray.add(hash.value().toString());
			}
		}
		logger.log(logger.HIGH, "Leaving ListManager.scanNoteForResources");
		return returnArray;
	}
	// Given a list of tags, produce a string list of tag names
	public String getTagNamesForNote(Note n) {
		StringBuffer buffer = new StringBuffer(100);
		Vector<String> v = new Vector<String>();
		List<String> guids = n.getTagGuids();
		
		if (guids == null) 
			return "";
		
		for (int i=0; i<guids.size(); i++) {
			v.add(getTagNameByGuid(guids.get(i)));
		}
		Comparator<String> comparator = Collections.reverseOrder();
		Collections.sort(v,comparator);
		Collections.reverse(v);
		
		for (int i = 0; i<v.size(); i++) {
			if (i>0) 
				buffer.append(", ");
			buffer.append(v.get(i));
		}
		
		return buffer.toString();
	}
	// Get a tag name when given a tag guid
	public String getTagNameByGuid(String guid) {
		for (int i=0; i<getTagIndex().size(); i++) {
			String s = getTagIndex().get(i).getGuid();
			if (s.equals(guid)) { 
				return getTagIndex().get(i).getName();
			}
		}
		return "";
	}
	// For a notebook guid, return the name
	public String getNotebookNameByGuid(String guid) {
		if (notebookIndex == null)
			return null;
		for (int i=0; i<notebookIndex.size(); i++) {
			String s = notebookIndex.get(i).getGuid();
			if (s.equals(guid)) { 
				return notebookIndex.get(i).getName();
			}
		}
		return "";
	}
	
	
	// Reload the note's tag names.  This is called when a tag's name changes by
	// the user.  It updates all notes with that tag to the new tag name.
	public void reloadNoteTagNames(String tagGuid, String newName) {
		
		// Set the master index
		for (int i=0; i<getMasterNoteIndex().size(); i++) {
			for (int j=0; j<getMasterNoteIndex().get(i).getTagGuids().size(); j++) {
				if (getMasterNoteIndex().get(i).getTagGuids().get(j).equals(tagGuid)) {
					getMasterNoteIndex().get(i).getTagNames().set(j, newName);
				}
			}
		}
		
		// Set the current index
		for (int i=0; i<getNoteIndex().size(); i++) {
			for (int j=0; j<getNoteIndex().get(i).getTagGuids().size(); j++) {
				if (getNoteIndex().get(i).getTagGuids().get(j).equals(tagGuid)) {
					getNoteIndex().get(i).getTagNames().set(j, newName);
				}
			}
		}
	}
	
}
