/*
 * This file is part of NixNote/NeighborNote 
 * Copyright 2009 Randy Baumgarte
 * 
 * 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.sql;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import com.evernote.edam.type.Tag;
import com.trolltech.qt.core.QBuffer;
import com.trolltech.qt.core.QByteArray;
import com.trolltech.qt.core.QIODevice;
import com.trolltech.qt.gui.QIcon;
import com.trolltech.qt.gui.QImage;
import com.trolltech.qt.gui.QPixmap;

import cx.fbn.nevernote.sql.driver.NSqlQuery;
import cx.fbn.nevernote.utilities.ApplicationLogger;

public class TagTable {
	private final ApplicationLogger logger;
	DatabaseConnection db;

	public TagTable (ApplicationLogger l, DatabaseConnection d) {
		logger = l;
		db = d;
	}
	// Create the table
	public void createTable() {
 		
		NSqlQuery query = new NSqlQuery(db.getConnection());
        logger.log(logger.HIGH, "Creating table Tag...");
        if (!query.exec("Create table Tag (guid varchar primary key, " +
        		"parentGuid varchar, sequence integer, hashCode integer, name varchar, isDirty boolean)"))
        	logger.log(logger.HIGH, "Table TAG creation FAILED!!!");  
 		
	}
	// Drop the table
	public void dropTable() {
 		
		NSqlQuery query = new NSqlQuery(db.getConnection());
		query.exec("Drop table Tag");
 		
	}
	// Get tags for a specific notebook
	// get all tags
	public List<Tag> getTagsForNotebook(String notebookGuid) {
 		
		Tag tempTag;
		List<Tag> index = new ArrayList<Tag>();
		boolean check;
						
        NSqlQuery query = new NSqlQuery(db.getConnection());
        				        
		check = query.prepare("Select guid, parentGuid, sequence, name"
				+" from Tag where notebookGuid=:notebookGuid");
		if (!check) {
			logger.log(logger.EXTREME, "Tag SQL prepare getTagsForNotebook has failed.");
			logger.log(logger.EXTREME, query.lastError());
		}
		query.bindValue(":notebookGuid", notebookGuid);
		query.exec();
		while (query.next()) {
			tempTag = new Tag();
			tempTag.setGuid(query.valueString(0));
			if (query.valueString(1) != null)
				tempTag.setParentGuid(query.valueString(1));
			else
				tempTag.setParentGuid(null);
			int sequence = new Integer(query.valueString(2)).intValue();
			tempTag.setUpdateSequenceNum(sequence);
			tempTag.setName(query.valueString(3));
			index.add(tempTag); 
		}	
 		
		return index;
	}
	// get all tags
	public List<Tag> getAll() {
 		
		Tag tempTag;
		List<Tag> index = new ArrayList<Tag>();
		boolean check;
						
        NSqlQuery query = new NSqlQuery(db.getConnection());
        				        
		check = query.exec("Select guid, parentGuid, sequence, name"
				+" from Tag where notebookguid not in (select guid from notebook where archived=true)");
		if (!check) {
			logger.log(logger.EXTREME, "Tag SQL retrieve has failed.");
			logger.log(logger.EXTREME, query.lastError());
		}
		while (query.next()) {
			tempTag = new Tag();
			tempTag.setGuid(query.valueString(0));
			if (query.valueString(1) != null)
				tempTag.setParentGuid(query.valueString(1));
			else
				tempTag.setParentGuid(null);
			int sequence = new Integer(query.valueString(2)).intValue();
			tempTag.setUpdateSequenceNum(sequence);
			tempTag.setName(query.valueString(3));
			index.add(tempTag); 
		}	
 		
		return index;
	}
	public Tag getTag(String guid) {
		Tag tempTag = new Tag();		
 		
        NSqlQuery query = new NSqlQuery(db.getConnection());
	        				        
		if (!query.prepare("Select guid, parentGuid, sequence, name"
				+" from Tag where guid=:guid"))
			logger.log(logger.EXTREME, "Tag select by guid SQL prepare has failed.");

		query.bindValue(":guid", guid);
		if (!query.exec())
			logger.log(logger.EXTREME, "Tag select by guid SQL exec has failed.");
		
		if (!query.next())  {
			return tempTag;
		}
		tempTag.setGuid(query.valueString(0));
		tempTag.setParentGuid(query.valueString(1));
		int sequence = new Integer(query.valueString(2)).intValue();
		tempTag.setUpdateSequenceNum(sequence);
		tempTag.setName(query.valueString(3));
		return tempTag;
	}
	// Update a tag
	public void updateTag(Tag tempTag, boolean isDirty) {
		updateTag(tempTag, isDirty, "");
	}
	// Update a tag
	public void updateTag(Tag tempTag, boolean isDirty, String realName) {
		boolean check;
		
        NSqlQuery query = new NSqlQuery(db.getConnection());
		check = query.prepare("Update Tag set parentGuid=:parentGuid, sequence=:sequence, "+
			"hashCode=:hashCode, name=:name, isDirty=:isDirty "
			+"where guid=:guid");
      
		if (!check) {
			logger.log(logger.EXTREME, "Tag SQL update prepare has failed.");
			logger.log(logger.EXTREME, query.lastError());
		}
		query.bindValue(":parentGuid", tempTag.getParentGuid());
		query.bindValue(":sequence", tempTag.getUpdateSequenceNum());
		query.bindValue(":hashCode", tempTag.hashCode());
		query.bindValue(":name", tempTag.getName());
		query.bindValue(":isDirty", isDirty);
		query.bindValue(":guid", tempTag.getGuid());
		
		check = query.exec();
		if (!check)
			logger.log(logger.MEDIUM, "Tag Table update failed.");
 		
	}
	// Delete a tag
	public void expungeTag(String guid, boolean needsSync) {
		boolean check;
		Tag t = getTag(guid);
		
 		
        NSqlQuery query = new NSqlQuery(db.getConnection());

       	check = query.prepare("delete from Tag "
   				+"where guid=:guid");
		if (!check) {
			logger.log(logger.EXTREME, "Tag SQL delete prepare has failed.");
			logger.log(logger.EXTREME, query.lastError());
		}
		query.bindValue(":guid", guid);
		check = query.exec();
		if (!check)
			logger.log(logger.MEDIUM, "Tag delete failed.");
		
       	check = query.prepare("delete from NoteTags "
   				+"where tagGuid=:guid");
		if (!check) {
			logger.log(logger.EXTREME, "NoteTags SQL delete prepare has failed.");
			logger.log(logger.EXTREME, query.lastError());
		}
		
		query.bindValue(":guid", guid);
		check = query.exec();
		if (!check)
			logger.log(logger.MEDIUM, "NoteTags delete failed.");
		
		// Add the work to the parent queue
		if (needsSync && t!= null && t.getUpdateSequenceNum() > 0) {
			DeletedTable del = new DeletedTable(logger, db);
			del.addDeletedItem(guid, "Tag");
		}
	}
	// Save a tag
	public void addTag(Tag tempTag, boolean isDirty) {
		addTag(tempTag, isDirty, false, "", "");
	}
	// Save a tag
	public void addTag(Tag tempTag, boolean isDirty, boolean isLinked, String realName, String notebookGuid) {
		boolean check;
 		
        NSqlQuery query = new NSqlQuery(db.getConnection());
		check = query.prepare("Insert Into Tag (guid, parentGuid, sequence, hashCode, name, isDirty, linked, realName, notebookGuid)"
				+" Values(:guid, :parentGuid, :sequence, :hashCode, :name, :isDirty, :linked, :realName, :notebookGuid)");
		if (!check) {
			logger.log(logger.EXTREME, "Tag SQL insert prepare has failed.");
			logger.log(logger.EXTREME, query.lastError());
		}
		query.bindValue(":guid", tempTag.getGuid());
		query.bindValue(":parentGuid", tempTag.getParentGuid());
		query.bindValue(":sequence", tempTag.getUpdateSequenceNum());
		query.bindValue(":hashCode", tempTag.hashCode());
		query.bindValue(":name", tempTag.getName());
		query.bindValue(":isDirty", isDirty);
		query.bindValue(":linked", isLinked);
		query.bindValue(":realName", realName);
		query.bindValue(":notebookGuid", notebookGuid);
		
		check = query.exec();
		if (!check) {
			logger.log(logger.MEDIUM, "Tag Table insert failed.");
			logger.log(logger.MEDIUM, query.lastError());
		}
	}
	// Update a tag's parent
	public void updateTagParent(String guid, String parentGuid) {
		boolean check;
 		
        NSqlQuery query = new NSqlQuery(db.getConnection());
		check = query.prepare("Update Tag set parentGuid=:parentGuid where guid=:guid");
		if (!check) {
			logger.log(logger.EXTREME, "Tag SQL tag parent update prepare has failed.");
			logger.log(logger.EXTREME, query.lastError());
		}

		query.bindValue(":parentGuid", parentGuid);
		query.bindValue(":guid", guid);
		
		check = query.exec();
		if (!check) {
			logger.log(logger.MEDIUM, "Tag parent update failed.");
			logger.log(logger.MEDIUM, query.lastError());
		}
	}
	//Save tags from Evernote
	public void saveTags(List<Tag> tags) {
		Tag tempTag;
		for (int i=0; i<tags.size(); i++) {
			tempTag = tags.get(i);
			addTag(tempTag, false);
		}		
	}
	// Update a tag sequence number
	public void updateTagSequence(String guid, int sequence) {
		boolean check;
        NSqlQuery query = new NSqlQuery(db.getConnection());
		check = query.prepare("Update Tag set sequence=:sequence where guid=:guid");
		query.bindValue(":sequence", sequence);
		query.bindValue(":guid", guid);
		
		query.exec();
		if (!check) {
			logger.log(logger.MEDIUM, "Tag sequence update failed.");
			logger.log(logger.MEDIUM, query.lastError());
		}
 		
	}
	// Update a tag sequence number
	public void updateTagGuid(String oldGuid, String newGuid) {
		boolean check;
 		
        NSqlQuery query = new NSqlQuery(db.getConnection());
		check = query.prepare("Update Tag set guid=:newGuid where guid=:oldGuid");
		query.bindValue(":newGuid", newGuid);
		query.bindValue(":oldGuid", oldGuid);
		query.exec();
		if (!check) {
			logger.log(logger.MEDIUM, "Tag guid update failed.");
			logger.log(logger.MEDIUM, query.lastError());
		}
		
		check = query.prepare("Update Tag set parentGuid=:newGuid where parentGuid=:oldGuid");
		query.bindValue(":newGuid", newGuid);
		query.bindValue(":oldGuid", oldGuid);
		query.exec();
		if (!check) {
			logger.log(logger.MEDIUM, "Tag guid update failed.");
			logger.log(logger.MEDIUM, query.lastError());
		}
		
		check = query.prepare("Update NoteTags set tagGuid=:newGuid where tagGuid=:oldGuid");
		query.bindValue(":newGuid", newGuid);
		query.bindValue(":oldGuid", oldGuid);
		query.exec();
		if (!check) {
			logger.log(logger.MEDIUM, "Tag guid update failed for NoteTags.");
			logger.log(logger.MEDIUM, query.lastError());
		}
 		
	}
	// Get dirty tags
	public List<Tag> getDirty() {
		Tag tempTag;
		List<Tag> index = new ArrayList<Tag>();
		boolean check;
						
 		
        NSqlQuery query = new NSqlQuery(db.getConnection());
        				        
		check = query.exec("Select guid, parentGuid, sequence, name"
				+" from Tag where isDirty = true");
		if (!check)
			logger.log(logger.EXTREME, "Tag SQL retrieve has failed.");
		while (query.next()) {
			tempTag = new Tag();
			tempTag.setGuid(query.valueString(0));
			tempTag.setParentGuid(query.valueString(1));
			int sequence = new Integer(query.valueString(2)).intValue();
			tempTag.setUpdateSequenceNum(sequence);
			tempTag.setName(query.valueString(3));
			if (tempTag.getParentGuid() != null && tempTag.getParentGuid().equals(""))
				tempTag.setParentGuid(null);
			index.add(tempTag); 
		}
		return index;
	}
	// Find a guid based upon the name
	public String findTagByName(String name) {
 		
		NSqlQuery query = new NSqlQuery(db.getConnection());
		
		query.prepare("Select guid from tag where name=:name");
		query.bindValue(":name", name);
		if (!query.exec())
			logger.log(logger.EXTREME, "Tag SQL retrieve has failed.");
		String val = null;
		if (query.next())
			val = query.valueString(0);
		return val;
	}
	// Get the linked notebook guid for this tag
	public String getNotebookGuid(String guid) {
 		
		NSqlQuery query = new NSqlQuery(db.getConnection());
		
		query.prepare("Select notebookguid from tag where guid=:guid");
		query.bindValue(":guid", guid);
		if (!query.exec())
			logger.log(logger.EXTREME, "Tag SQL retrieve has failed.");
		String val = null;
		if (query.next())
			val = query.valueString(0);
		return val;
	}
	// given a guid, does the tag exist
	public boolean exists(String guid) {
 		
		NSqlQuery query = new NSqlQuery(db.getConnection());
		
		query.prepare("Select guid from tag where guid=:guid");
		query.bindValue(":guid", guid);
		if (!query.exec())
			logger.log(logger.EXTREME, "Tag SQL retrieve has failed.");
		boolean retval = query.next();
		return retval;
	}
	// This is a convience method to check if a tag exists & update/create based upon it
	public void syncLinkedTag(Tag tag, String notebookGuid, boolean isDirty) {
		if (exists(tag.getGuid())) {
			Tag t = getTag(tag.getGuid());
			String realName = tag.getName();
			tag.setName(t.getName());
			updateTag(tag, isDirty, realName);
		}
		else
			addTag(tag, isDirty, true, tag.getName(), notebookGuid);
	}

	// This is a convience method to check if a tag exists & update/create based upon it
	public void syncTag(Tag tag, boolean isDirty) {
		if (exists(tag.getGuid()))
			updateTag(tag, isDirty);
		else
			addTag(tag, isDirty);
	}
	public void  resetDirtyFlag(String guid) {
 		
		NSqlQuery query = new NSqlQuery(db.getConnection());
		
		query.prepare("Update tag set isdirty=false where guid=:guid");
		query.bindValue(":guid", guid);
		if (!query.exec())
			logger.log(logger.EXTREME, "Error resetting tag dirty field.");
	}
	
	
	// Get the custom icon
	public QIcon getIcon(String guid) {
		NSqlQuery query = new NSqlQuery(db.getConnection());
		
		if (!query.prepare("Select icon from tag where guid=:guid"))
			logger.log(logger.EXTREME, "Error preparing tag icon select.");
		query.bindValue(":guid", guid);
		if (!query.exec())
			logger.log(logger.EXTREME, "Error finding tag icon.");
		if (!query.next() || query.getBlob(0) == null)
			return null;
		
		QByteArray blob = new QByteArray(query.getBlob(0));
		QIcon icon = new QIcon(QPixmap.fromImage(QImage.fromData(blob)));
		return icon;
	}
	// Set the custom icon
	public void setIcon(String guid, QIcon icon, String type) {
		NSqlQuery query = new NSqlQuery(db.getConnection());
		if (icon == null) {
			if (!query.prepare("update tag set icon=null where guid=:guid"))
				logger.log(logger.EXTREME, "Error preparing tag icon update.");
		} else {
			if (!query.prepare("update tag set icon=:icon where guid=:guid"))
				logger.log(logger.EXTREME, "Error preparing tag icon update.");
			QBuffer buffer = new QBuffer();
	        if (!buffer.open(QIODevice.OpenModeFlag.ReadWrite)) {
	        	logger.log(logger.EXTREME, "Failure to open buffer.  Aborting.");
	        	return;
	        }
	        QPixmap p = icon.pixmap(32, 32);
	        QImage i = p.toImage();
	       	i.save(buffer, type.toUpperCase());
	       	buffer.close();
	       	QByteArray b = new QByteArray(buffer.buffer());
	       	if (!b.isNull() && !b.isEmpty())
	       		query.bindValue(":icon", b.toByteArray());
	       	else
	       		return;
		}
		query.bindValue(":guid", guid);
		if (!query.exec()) 
			logger.log(logger.LOW, "Error setting tag icon. " +query.lastError());
	}

	// Get a list of all icons
	public HashMap<String, QIcon> getAllIcons() {
		HashMap<String, QIcon> values = new HashMap<String, QIcon>();
		NSqlQuery query = new NSqlQuery(db.getConnection());
	
		if (!query.exec("SELECT guid, icon from tag"))
			logger.log(logger.EXTREME, "Error executing SavedSearch getAllIcons select.");
		while (query.next()) {
			if (query.getBlob(1) != null) {
				String guid = query.valueString(0);
				QByteArray blob = new QByteArray(query.getBlob(1));
				QIcon icon = new QIcon(QPixmap.fromImage(QImage.fromData(blob)));
				values.put(guid, icon);
			}
		}
		return values;
	}

	// Remove unused tags that are linked tags
	public void removeUnusedLinkedTags() {
		NSqlQuery query = new NSqlQuery(db.getConnection());
		
		query.exec("Delete from tag where linked=true and guid not in (select distinct tagguid from notetags);");
	}
	
	public void cleanupTags() {
		NSqlQuery query = new NSqlQuery(db.getConnection());
		
		query.exec("Update tag set parentguid=null where parentguid not in (select distinct guid from tag);");	
	}

	
	public List<String> findChildren(String guid, List<Tag> tagList) {
		List<String> returnValue = new ArrayList<String>();
		
		for (int i=0; i<tagList.size(); i++) {
			if (tagList.get(i).getParentGuid().equalsIgnoreCase(guid)) {
				returnValue.add(tagList.get(i).getName());
				List<String> childMatch = findChildren(tagList.get(i).getGuid(), tagList);
				for (int j=0; j<childMatch.size(); j++) {
					returnValue.add(childMatch.get(j));
				}
			}
		}
		return returnValue;
	}
}
