package org.ourfilesystem.filehander;

/*
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.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map.Entry;

import org.ourfilesystem.db.LocalFileReference;
import org.ourfilesystem.utilities.BBytes;
import org.ourfilesystem.utilities.FileUtils;

public class PendingMultiDownload {
	
	public static int CURRENT_FORMAT_VERSION = 0x1;
	
	public static int PENDING_PRIMARY_DOWNLOAD = 0; //We haven't gotten the first file.
	public static int PENDING_PEICES = 1;
	public static int COMPLETE = 2;
	
	private int Mode = 0;
	private File SaveAs;

	private BBytes PrimaryPeice;
	private HashMap<BBytes, Boolean> PeicesReceived;
	private long FinalSize;
	private long DownloadedSize;

	private boolean PrimaryRequested;
	private HashMap<BBytes, Boolean> PeicesRequested;
	
	private boolean Paused;
	
	private int Priority;
	
	public PendingMultiDownload() {
		PeicesReceived = new HashMap<BBytes,Boolean>();
		PeicesRequested = new HashMap<BBytes,Boolean>();
	}
	
	public File getSaveAs() {
		return SaveAs;
	}
	
	/**
	 * After creating a new object of this type we have to call
	 * this to initialize it.
	 * @param sa The file to save as.
	 * @param primary the primary file's digest.
	 * @throws IOException
	 */
	public synchronized void Init(File sa, BBytes primary, int priority, boolean paused) throws IOException {
		Priority = priority;
		Paused = paused;
		SaveAs = sa;
		PrimaryPeice = primary;
		if (!SaveAs.exists()) {
			SaveAs.createNewFile();
		}
	}

	/**
	 * On a program restart this must be called to reset the
	 * request status of the parts.  Old requests do not
	 * persist in the core.
	 */
	public synchronized void ResetRequests() {
		PrimaryRequested = false;
		Iterator<BBytes> i = PeicesRequested.keySet().iterator();
		while (i.hasNext()) {
			BBytes k = i.next();
			if (!PeicesReceived.get(k)) {
				PeicesRequested.put(k, false);
			}
			else {
				PeicesRequested.put(k, true);
			}
		}
	}
	
	/**
	 * This is called by the filehanlder to get the next part
	 * to download if any have not been requested and received yet.
	 * @return
	 */
	public synchronized BBytes nextPeiceToDownload() {
		if (Mode == COMPLETE) {
			return null;
		}
		if (Mode == PENDING_PRIMARY_DOWNLOAD) {
			if (!PrimaryRequested) {
				PrimaryRequested = true;
				return PrimaryPeice;
			}
			else {
				return null;
			}
		}
		Iterator<Entry<BBytes,Boolean>> i = PeicesReceived.entrySet().iterator();
		while (i.hasNext()) {
			Entry<BBytes,Boolean> e = i.next();
			if (!e.getValue()) {
				if (!PeicesRequested.get(e.getKey())) {
					PeicesRequested.put(e.getKey(), true);
					return e.getKey();
				}
			}
		}
		return null;
	}
	
	public synchronized void clearPending(BBytes dig) {
		Boolean val = PeicesRequested.get(dig);
		if (val != null && val) {
			PeicesRequested.put(dig, false);
		}
	}
	
	/**
	 * Have we completed downloading and saving all parts of this file yet?
	 * @return
	 */
	public boolean isComplete() {
		if (Mode == COMPLETE) {
			return true;
		}
		return false;
	}
	
	/**
	 * The primary file has returned.  Lets read it to get the digests for
	 * all other parts of this file that we need.
	 * @param f The primary file.
	 * @throws IOException
	 */
	private synchronized boolean processPrimaryFile(File f) throws IOException {
		boolean used = false;
		FileInputStream fis = new FileInputStream(f);
		int v = fis.read();
		if (v == CURRENT_FORMAT_VERSION) {
			FinalSize = FileUtils.readLong(fis);
			int peices = FileUtils.readInt(fis);
			for (int cnt = 0; cnt < peices; cnt++) {
				byte b[] = FileUtils.readBytes(fis);
				BBytes bb = new BBytes(b);
				PeicesReceived.put(bb, false);
				PeicesRequested.put(bb, false);
				Mode = PENDING_PEICES;
				used = true;
			}
		}
		fis.close();
		return used;
	}
	
	public synchronized int getNumberRequested() {
		int r = 0;
		Iterator<BBytes> i = PeicesRequested.keySet().iterator();
		while (i.hasNext()) {
			BBytes k = i.next();
			if (PeicesRequested.get(k)) {
				if (!PeicesReceived.get(k)) {
					r++;
				}
			}
		}
		return r;
	}
	
	/**
	 * Process a new file that has just been downloaded.  If the fragment
	 * does not belong to this file then it is ignored. 
	 * @param ref
	 */
	public synchronized boolean ProcessPiece(LocalFileReference ref) {
		boolean used = false;
		if (Mode == PENDING_PRIMARY_DOWNLOAD) {
			if (PrimaryPeice.equals(ref.getFileReference().getUnsignedDigest())) {
				try {
					if (processPrimaryFile(ref.getFileReference().getFile())) {
						used = true;
					}
				}
				catch (Exception e) {
					e.printStackTrace();
				}
			}
			else {
				return false;
			}
		}
		else if (Mode == PENDING_PEICES){
			Boolean checkit = PeicesReceived.get(ref.getFileReference().getUnsignedDigest());
			if (checkit != null && !checkit) {
				try {
					FileSplitter.insert(SaveAs, ref.getFileReference().getFile());
					DownloadedSize += ref.getFileReference().getFile().length() - (2*(Long.SIZE/Byte.SIZE));
					PeicesReceived.put((BBytes)ref.getFileReference().getUnsignedDigest(), true);
					used = true;
					//-- if no peices received are false then we're done.
					if (!PeicesReceived.containsValue(false)) {
						Mode = COMPLETE;
					}
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
		return used;
	}
	
	public long getCompleteSize() {
		return FinalSize;
	}
	
	public long getDownloadedSize() {
		return DownloadedSize;
	}

	public boolean isPaused() {
		return Paused;
	}

	public void setPaused(boolean paused) {
		Paused = paused;
	}
	
	public synchronized boolean isInProgress() {
		Iterator<Entry<BBytes,Boolean>> i = PeicesRequested.entrySet().iterator();
		while (i.hasNext()) {
			Entry<BBytes,Boolean> e = i.next();
			if (e.getValue()) { //-- requested
				if (!PeicesReceived.get(e.getKey())) { //-- but not received
					return true;
				}
			}
		}
		return false;
	}

	public int getPriority() {
		return Priority;
	}

	public void setPriority(int priority) {
		Priority = priority;
	}

}
