/**
 * 
         -------- Copyright (C) [2012]  [Kristofer SCHWEIGER & Niklas FLORIAN1]---------

    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 St, Fifth Floor, Boston, MA 02110, USA

 * 
 * For any licensing issues please contact us under following adress: Mosaik.Projekt.2012@gmail.com
 */

//

package dataBase;

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

import javax.imageio.ImageIO;

import util.Tools;
import util.io.FilesAndFolders;
import util.simpleIO.Out;

import dataBase.search.Best1List;
import dataBase.search.SearchList;
import dataBase.search.comparator.*;
import dataBase.search.comparator.hash.*;
import dataBase.search.distance.AbstractDistance;
import dataBase.search.distance.Distance_L1;
import dataBase.subPicture.SubPicture;


public class DataBase implements Serializable {
	
	private static final long serialVersionUID = 1L;
	public ArrayList<SubPicture> list = new ArrayList<SubPicture>(500);
	
	@SuppressWarnings("unchecked")
	private static Comparator<SubPicture>[] hashComparators = new Comparator[] {
			new ComparatorRGB_R(),
			new ComparatorRGB_G(),
			new ComparatorRGB_B(),
//			new ComparatorHSV_H(),
//			new ComparatorHSV_S(),
//			new ComparatorHSV_V(),
//			new ComparatorRGB_Lumi(),
	};
	/** transient: will and can not be serialized **/
	transient private ArrayList<SearchList> searchLists = new ArrayList<SearchList>(hashComparators.length);
	
	public static AbstractDistance searchDistance = new Distance_L1();
	public static Comparator<SubPicture> searchComparator;  // = new ComperatorSubPic();
	

	/**
	 * Constructor: Full
	 * @param dir
	 * @throws IOException
	 */
	public DataBase(File dir) throws IOException{
		Out.pl("> Adding pictures from dir to DB:");
		addPicturesFromDir(dir);
		Out.pl("> Making search list:");
		makeSearchLists();
	}
	public DataBase(){
		searchLists = new ArrayList<SearchList>(hashComparators.length);
	}
	
	
	/**
	 * Test Constructor
	 * @throws IOException
	 */
	protected DataBase(boolean test) throws IOException{
		
		File imageFile = new File("testdatenfinalImg.jpg");
		BufferedImage image = ImageIO.read(imageFile);
		
		list.add(new SubPicture(imageFile, image));
		list.add(new SubPicture(imageFile, image));
		list.add(new SubPicture(imageFile, image));
		list.add(new SubPicture(imageFile, image));
	}
	
	/**
	 * 
	 * @param searchPic
	 * @return 
	 */
	public ArrayList<SubPicture> search(SubPicture searchPic, int threadID){
		
		Best1List<SubPicture> results = new Best1List<SubPicture>(new ComperatorSubPic(threadID));
		
		for(SearchList searchList : searchLists){
			searchList.getBest(searchPic, results, threadID);
		}
		results.sortAndLimitWithoutDoubleEntries();
		
		return results.getList();
	}
	
	/**
	 * Add another DataBase.
	 * @param DBs
	 */
	public void add(DataBase... DBs){
		
		for(DataBase db : DBs){
			list.addAll(db.list);
		}
		makeSearchLists();
	}
	
	
	final static int threadDelay=50;
	
	/**
	 * Get pictures for database.
	 * @param dir
	 * @throws IOException 
	 */
	public void addPicturesFromDir(File dir) throws IOException{
		
		int processors = Tools.getAvailableProcessors();
		//processors = Math.min(2, processors);				// for slow storage
		ArrayList<File> pics = FilesAndFolders.listPicturesRec(dir);
		Out.p(" >> Start: ");
		Thread[] threads = new Thread[(int)processors];
		
		// --- split work between processors ---
		//
		for(int i=0; i<processors; i++){
			
			float partSize = pics.size()/(float)processors;
			int from = (int) ((i)   * (partSize) +0.5f);
			int to   = (int) ((i+1) * (partSize) +0.5f);
			
			final List<File> tmp = pics.subList(from, to);
			//Out.pl("Thread got image from "+from+" to "+to+".");
			//final int threadID = i;
			threads[i] = new TimedAction(threadDelay){
				@Override
				public void action() throws Exception {
					//int c=0;
					for(File imageFile : tmp){
						try{
							BufferedImage image = ImageIO.read(imageFile);
							//TODO: other image loader if null
							
							SubPicture pic = new SubPicture(imageFile, image);
							synchronized (list) {
								list.add(pic);
							}
							if(list.size()%500==0)
								Out.pl("  <database has "+list.size()+" pics> ");
						}
						catch(Exception e){
							System.err.println("Error at loading file: "+imageFile+" ("+e+")");
						}

					}
				}
			};
		}
		Out.pl();
		
		// --- join ---
		//
		for(int i=0; i<processors; i++){
			try {
				threads[i].join();
			} catch (InterruptedException e) {
				// ignore?
			}
		}
		
	}
	
	/**
	 * Builds sorted search lists for finding similar pictures.
	 */
	public void makeSearchLists(){
		searchLists.clear();
		
		for(Comparator<SubPicture> c : hashComparators){
			SearchList sl = new SearchList(this, c);
			searchLists.add(sl);
		}
	}
	
	
	@Override
	public String toString(){
		return "Data Base with "+list.size()+" SubPictures and "+searchLists.size()+" search lists.";
	}
	public String toString2(){
		String s = "List with "+list.size()+" SubPictures: \n";
		
		for(SubPicture p : list){
			s += "  "+p.toString()+"\n";
		}
		
		return s;
	}
	
	/**
	 * @throws IOException
	 */
	private void writeObject(java.io.ObjectOutputStream out) throws IOException {
		out.defaultWriteObject();
	}
	/**
	 * 
	 */
	private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
		in.defaultReadObject();
		
		searchLists = new ArrayList<SearchList>(hashComparators.length);
		makeSearchLists();
	}
}
