package org.ourfilesystem.ui.db;

/*
OurFileSystem is a peer2peer file sharing program.
Copyright (C) 2012  Robert Gass

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

*/

import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.Set;

import org.ourfilesystem.com.ConnectionUpdateInterface;
import org.ourfilesystem.core.EventInterface;
import org.ourfilesystem.db.LocalFileReference;
import org.ourfilesystem.db.LocalPost;
import org.ourfilesystem.db.Peer;
import org.ourfilesystem.postcodec.Codec;
import org.ourfilesystem.postcodec.PostDecoded;
import org.ourfilesystem.utilities.BBytes;

import com.db4o.Db4oEmbedded;
import com.db4o.ObjectContainer;
import com.db4o.ObjectSet;
import com.db4o.query.Predicate;

public class DBImpl implements DBInterface, EventInterface {
	
	//TODO: Make sure to add a testcase to check for the * regular expression checking.
	
	private ObjectContainer DB;
	
	public DBImpl(String file) {
		DB = Db4oEmbedded.openFile(file);
		Runtime.getRuntime().addShutdownHook(new Thread() {
			public void run() {
				DB.close();
			}
		});
	}

	@SuppressWarnings("serial")
	@Override
	public void saveNewPost(final PostDecoded p) {
		//First check if we already have the post.
		ObjectSet<PostDecoded> os = DB.query(new Predicate<PostDecoded>() {
			@Override
			public boolean match(PostDecoded c) {
				if (c.getPost().getPost().equals(p.getPost().getPost())) {
					return true;
				}
				return false;
			}
		});
		if (os.size() == 0)	{
			DB.store(p);
			DB.commit();
		}
	}

	@SuppressWarnings("serial")
	@Override
	public QueryResultsInterface search(final HashMap<String, HashSet<String>> orstring,
										final HashMap<String, HashSet<String>> andstring, 
										final HashMap<String,HashSet<String>>  excludestrings,
										final HashMap<String, HashSet<Long>>   ornum,
										final HashMap<String, Long>            andnums, 
										final HashMap<String, Long>            lessthan,
										final HashMap<String, Long>            greaterthan, 
										final HashMap<String,Long> averagelessthan,
										final HashMap<String,Long> averagegreaterthan,
										final HashSet<String>                  excludebackrefs,
										final HashMap<String, HashSet<Object>> hasreferences,
										final HashSet<Object>                  directrefs,
										final HashSet<BBytes>                  includepeers,
										final HashSet<BBytes>                  excludepeers,
										final Date                             fromd, 
										final Date                             tod
										) {
		
		final HashSet<BBytes> excludebad = new HashSet<BBytes>();
		final HashSet<BBytes> excluderefs = new HashSet<BBytes>();
		final HashSet<BBytes> includerefs = new HashSet<BBytes>();
		
		ObjectSet<PostDecoded> os = DB.query(new Predicate<PostDecoded>() {
			@Override
			public boolean match(PostDecoded p) {
				boolean ok = true;
				
				//------------------------------------------------------------------------
				//exclude strings
				System.out.println("excludestrings: " + excludestrings);
				if (excludestrings != null) {
					Iterator<Entry<String,HashSet<String>>> i = excludestrings.entrySet().iterator();
					while (i.hasNext()) {
						Entry<String,HashSet<String>> e = i.next();
						System.out.println(">> K: " + e.getKey() + " :: " + e.getValue());
						Set<String> ks = p.getStringKeySet(e.getKey());
						System.out.println("    post: " + ks);
						if (ks != null) {
							Iterator<String> i2 = e.getValue().iterator();
							while (i2.hasNext()) {
								String v = i2.next();
								v = v.toLowerCase();
								v = v.replaceAll("\\*", ".*");
								Matcher m = Pattern.compile(v).matcher("");
								Iterator<String> stri = ks.iterator();
								while (stri.hasNext()) {
									String s = stri.next();
									s = s.toLowerCase();
									m.reset(s);
									if (m.find()) {
										BBytes ref = (BBytes) p.getPost().getPost().getFileReferenceDigest();
										if (ref != null) {
											synchronized (excludebad) {
												excludebad.add(ref);
											}
										}
										return false;
									}
								}
							}
						}
					}
				}
				
				//------------------------------------------------------------------------
				//exclude files that have been referenced by other posts other references
				// list with his key value.
				if (excludebackrefs != null) {
					Iterator<String> i = excludebackrefs.iterator();
					while (i.hasNext()) {
						String k = i.next();
						Set<Object> os = p.getOtherReferences().get(k);
						if (os != null) {
							Iterator<Object> i2 = os.iterator();
							while (i2.hasNext()) {
								BBytes ref = (BBytes)i2.next();
								if (ref != null) {
									BBytes br = (BBytes)p.getPost().getPost().getFileReferenceDigest();
									if (br != null) {
										synchronized (excluderefs) {
											excluderefs.add(br);
										}
										return false;
									}
									synchronized (includerefs) {
										includerefs.add(ref);
									}
								}
							}
						}
					}
				}

				//------------------------------------------------------------------------
				//check dates.
				if (fromd != null) {
					if (p.getPost().getLocalDate().compareTo(fromd) < 0) {
						return false;
					}
				}
				if (tod != null) {
					if (p.getPost().getLocalDate().compareTo(tod) > 0) {
						return false;
					}
				}
				
				//------------------------------------------------------------------------
				// check direct refs.
				if (directrefs != null && directrefs.size() > 0) {
					if (!directrefs.contains(p.getPost().getPost().getFileReferenceDigest())) {
						return false;
					}
				}
				
				//------------------------------------------------------------------------
				//check peer lists.
				if (includepeers != null && includepeers.size() > 0) {
					BBytes peerid = (BBytes)p.getPost().getPost().getSignedDigest().getPeerIdentifier();
					if (!includepeers.contains(peerid)) return false;
				}
				
				if (excludepeers != null && excludepeers.size() > 0) {
					BBytes peerid = (BBytes)p.getPost().getPost().getSignedDigest().getPeerIdentifier();
					if (excludepeers.contains(peerid)) return false;
				}
				
				//------------------------------------------------------------------------
				//check or list.
				if (orstring != null) {
					Iterator<Entry<String,HashSet<String>>> i = orstring.entrySet().iterator();
					while (i.hasNext()) {
						Entry<String,HashSet<String>> e = i.next();
						if (e.getValue() != null && e.getValue().size() > 0) {
							Set<String> ks = p.getStringKeySet(e.getKey());
							boolean thisoneok = false;
							Iterator<String> i2 = e.getValue().iterator();
							while (i2.hasNext() && !thisoneok) {
								String v = i2.next();
								v = v.toLowerCase();
								if (ks != null) {
									v = v.replaceAll("\\*", ".*");
									Matcher m = Pattern.compile(v).matcher("");
									Iterator<String> stri = ks.iterator();
									while (stri.hasNext() && !thisoneok) {
										String s = stri.next();
										s = s.toLowerCase();
										m.reset(s);
										if (m.find()) {
											thisoneok = true;
										}
									}

								}
							}
							if (!thisoneok) {
								return false;
							}
						}
					}
				}
				
				//------------------------------------------------------------------------
				//check and list.
				if (andstring != null) {
					Iterator<Entry<String,HashSet<String>>> i = andstring.entrySet().iterator();
					while (i.hasNext()) {
						Entry<String,HashSet<String>> e = i.next();
						Set<String> ks = p.getStringKeySet(e.getKey());
						if (e.getValue() != null && e.getValue().size() > 0) {
							if (ks == null) return false;
							Iterator<String> stri = e.getValue().iterator();
							while (stri.hasNext()) {
								String v = stri.next();
								v = v.toLowerCase();
								v = v.replaceAll("\\*", ".*");
								Matcher m = Pattern.compile(v).matcher("");
								Iterator<String> sti = ks.iterator();
								boolean fnd = false;
								while (sti.hasNext() && !fnd) {
									String s = sti.next();
									s = s.toLowerCase();
									m.reset(s);
									if (m.find()) fnd = true;
								}
								if (!fnd) {
									return false;
								}
							}
						}
					}
				}
				
				//------------------------------------------------------------------------
				//check or list.
				if (ornum != null) {
					Iterator<Entry<String,HashSet<Long>>> i = ornum.entrySet().iterator();
					while (i.hasNext()) {
						Entry<String,HashSet<Long>> e = i.next();
						if (e.getValue() != null && e.getValue().size() > 0) {
							Long ks = p.getNumberValues().get(e.getKey());
							if (ks != null) {
								if (!e.getValue().contains(ks)) {
									return false;
								}
							}
							else {
								return false;
							}
						}
					}
				}
				
				//------------------------------------------------------------------------
				//check and list.
				if (andnums != null) {
					Iterator<Entry<String,Long>> i = andnums.entrySet().iterator();
					while (i.hasNext()) {
						Entry<String,Long> e = i.next();
						Long ks = p.getNumberValues().get(e.getKey());
						if (e.getValue() != null) {
							if (ks == null) return false;
							if (!ks.equals(e.getValue())) return false;
						}
					}
				}
				
				//------------------------------------------------------------------------
				//check lessthan values.
				if (lessthan != null) {
					Iterator<Entry<String,Long>> i = lessthan.entrySet().iterator();
					while (i.hasNext()) {
						Entry<String,Long> e = i.next();
						Long v = p.getNumberValues().get(e.getKey());
						if (e.getValue() != null) {
							if (v == null) return false;
							if (e.getValue().compareTo(v) < 0) {
								return false;
							}
						}
					}
				}

				//------------------------------------------------------------------------
				//check greaterthan values.
				if (greaterthan != null) {
					Iterator<Entry<String,Long>> i = greaterthan.entrySet().iterator();
					while (i.hasNext()) {
						Entry<String,Long> e = i.next();
						Long v = p.getNumberValues().get(e.getKey());
						if (e.getValue() != null) {
							if (v == null) return false;
							if (e.getValue().compareTo(v) > 0) {
								return false;
							}
						}
					}
				}
				
				//------------------------------------------------------------------------
				//only include ones with these references.
				if (hasreferences != null) {
					int cnt = 0;
					boolean hasref = false;
					Iterator<Entry<String,HashSet<Object>>> i = hasreferences.entrySet().iterator();
					while (i.hasNext() && !hasref) {
						Entry<String,HashSet<Object>> e = i.next();
						String k = e.getKey();
						Set<Object> refs = e.getValue();
						Set<Object> prefs = p.getOtherReferences().get(k);
						if (refs != null) {
							Iterator<Object> i2 = refs.iterator();
							while (i2.hasNext() && !hasref) {
								Object r = i2.next();
								cnt++;
								if (prefs != null) {
									if (prefs.contains(r)) { hasref = true; }
								}
							}
						}
					}
					if (cnt == 0) hasref = true;
					if (!hasref) return false;
				}

				return ok;
			}
		});
		
		LinkedList<PostDecoded> rl = new LinkedList<PostDecoded>();

		//------------------------------------------------------------------------
		// remove includes from the excludes list.  This will make all circular
		// references show up.
		excluderefs.removeAll(includerefs);
		
		//------------------------------------------------------------------------
		//Make sure we remove back references and exclude refs.
		Iterator<PostDecoded> ip = os.iterator();
		while (ip.hasNext()) {
			PostDecoded p = ip.next();
			if (excludebackrefs != null || excluderefs.size() > 0) {
				boolean remove = false;
				BBytes ref = (BBytes)p.getPost().getPost().getFileReferenceDigest();
				if (ref != null) {
					if (excluderefs.contains(ref)) {
						remove = true;
					}
					if (excludebad.contains(ref)) {
						remove = true;
					}
				}
				if (!remove) {
					rl.add(p);
				}
			}
			else {
				rl.add(p);
			}
		}

		QueryResultsImpl r = new QueryResultsImpl(rl);
		return r;
	}

	@Override
	public void newPostReceived(LocalPost postlist) {
		try {
			PostDecoded pd = Codec.decode(postlist);
			saveNewPost(pd);
		}
		catch (Exception e) {
			e.printStackTrace();
		}
	}

	@SuppressWarnings("serial")
	@Override
	public synchronized void newPeerReceived(final Peer peer) {
		ObjectSet<Peer> os = DB.query(new Predicate<Peer>() {
			@Override
			public boolean match(Peer p) {
				if (p.getPeerKeysAndIdentity().getSignature().getDigest().equals(
						peer.getPeerKeysAndIdentity().getSignature().getDigest())) return true;
				return false;
			}
		});
		Iterator<Peer> i = os.iterator();
		while (i.hasNext()) {
			DB.delete(i.next());
		}
		DB.store(peer);
		DB.commit();
	}

	@Override
	public void newFileDownloaded(LocalFileReference filelist) {
		//-- i don't care.
	}

	@Override
	public void downloadFailed(Object dig) {
		//-- i don't care.
	}

	@Override
	public void connectionFailure(Peer p) {
		//-- i don't care.
	}

	@SuppressWarnings("serial")
	@Override
	public synchronized Peer searchPeer(final Object id) {
		ObjectSet<Peer> os = DB.query(new Predicate<Peer>() {
			@Override
			public boolean match(Peer p) {
				if (p.getPeerKeysAndIdentity().getSignature().getDigest().equals(id)) return true;
				return false;
			}
		});
		if (os.size() > 0) {
			Iterator<Peer> i = os.iterator();
			return i.next();
		}
		return null;
	}

	@Override
	public void connectionEvent(ConnectionUpdateInterface con) {
		//I don't care.
	}

}

