package hsr.dsa.network;

import java.io.IOException;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;

import org.apache.log4j.Logger;

import hsr.dsa.data.DataCallback;
import hsr.dsa.network.data.DataObject;
import hsr.dsa.network.data.PublicProfile;
import hsr.dsa.network.exceptions.DublicateEntryException;
import net.tomp2p.dht.FutureGet;
import net.tomp2p.dht.FuturePut;
import net.tomp2p.futures.BaseFutureListener;
import net.tomp2p.peers.Number160;
import net.tomp2p.peers.Number640;
import net.tomp2p.storage.Data;

public class DataClient extends NetworkClient {
	private static final Logger log = Logger.getLogger(DataClient.class);
	public DataClient(short port, InetAddress rootNode, short rootPort, NetworkEventHandler eventHandler,
			Number160 address) throws IOException {
		super(port, rootNode, rootPort);
	}

	public DataClient(short port, InetAddress rootNode, short rootPort) throws IOException {
		super(port, rootNode, rootPort);
	}

	public <T extends DataObject> void get(Number160 id, Class<T> clazz, DataCallback<T> callback)
			throws ClassNotFoundException, IOException {
		log.debug("DHT GET: " + clazz.getName() + " at " + id);
		this.getPeer().get(id).start().addListener(new BaseFutureListener<FutureGet>() {
			@Override
			public void operationComplete(FutureGet future) throws Exception {
				if (future.isSuccess()) {
					for (Entry<Number640, Data> entry : future.dataMap().entrySet()) {
						Data data = entry.getValue();
						if (!data.isEmpty()) {
							Object object = data.object();
							if (clazz.isInstance(object)) {
								callback.onData(clazz.cast(object));
								return;
							}
						}
					}
				}
				callback.onData(null);
			}

			@Override
			public void exceptionCaught(Throwable t) throws Exception {
				log.error("exception Caught", t);
				callback.onData(null);
			}
		});
	}

	public <T extends DataObject> void getAll(Number160 id, Class<T> clazz, DataCallback<List<T>> callback)
			throws ClassNotFoundException, IOException {
		this.getAll(id, clazz, false, callback);
	}
	
	public <T extends DataObject> void getAll(Number160 id, Class<T> clazz, boolean remove,
			DataCallback<List<T>> callback) throws ClassNotFoundException, IOException {
		log.debug("DHT GETALL: " + clazz.getName() + " at " + id);
		getPeer().get(id).all().start().addListener(new BaseFutureListener<FutureGet>() {
			@Override
			public void operationComplete(FutureGet fget) throws Exception {
				final List<T> elements = new ArrayList<T>();
				if (fget.isSuccess() && !fget.isEmpty()) {
					Set<Entry<Number640, Data>> set = fget.dataMap().entrySet();
					for (Entry<Number640, Data> entry : set) {
						Data dataId = entry.getValue();
						if (!dataId.isEmpty()) {
							if (clazz.isInstance(dataId.object())) {
								synchronized (elements) {
									elements.add(clazz.cast(dataId.object()));
								}
								if (remove) {
									getPeer().remove(id).contentKey(entry.getKey().contentKey()).start();
								}
							}
						}
					}
					callback.onData(elements);
				} else {
					callback.onData(elements);
				}
			}

			@Override
			public void exceptionCaught(Throwable t) throws Exception {
				log.error("exception Caught", t);
			}
		});
	}

	public void put(Number160 id, DataObject value, DataCallback<Boolean> callback) throws IOException {
		log.debug("DHT PUT: " + value.toString() + " at " + id);
			try {
				getPeer().put(id).data(new Data(value)).start().addListener(new BaseFutureListener<FuturePut>() {
					@Override
					public void operationComplete(FuturePut future) throws Exception {
						callback.onData(future.isSuccess());
					}

					@Override
					public void exceptionCaught(Throwable t) throws Exception {
						callback.onData(false);
					}
				});
			} catch (IOException e) {
				log.error("exception Caught", e);
			}
		//});
	}
	
	public <T extends DataObject> void removeAll(Number160 id, Class<T> clazz, DataCallback<Boolean> callback) throws IOException {
		getPeer().get(id).start().addListener(new BaseFutureListener<FutureGet>() {
			@Override
			public void operationComplete(FutureGet future) throws Exception {
				try {
					if (future.isSuccess()) {
						for (Entry<Number640, Data> entry : future.dataMap().entrySet()) {
							Data data = entry.getValue();
							if (!data.isEmpty()) {
								Object object = data.object();
								if (clazz.isInstance(object)) {
									getPeer().remove(id).contentKey(entry.getKey().contentKey()).start();
								}
							}
						}
					}
				} finally {
					callback.onData(true);
				}
			}
			
			@Override
			public void exceptionCaught(Throwable t) throws Exception {
				log.error("exception Caught", t);
				callback.onData(false);
			}
		});
	}

	public void add(Number160 id, DataObject value) throws IOException {
		log.debug("DHT ADD: " + value.toString() + " at " + id);
		this.getPeer().add(id).data(new Data(value)).start();
	}

	public void getProfile(String username, DataCallback<PublicProfile> callback)
			throws ClassNotFoundException, IOException {
		this.getProfile(Number160.createHash(username), callback);
	}

	public void getProfile(Number160 id, DataCallback<PublicProfile> callback)
			throws ClassNotFoundException, IOException {
		this.get(id, PublicProfile.class, callback);
		
	}

	public void createProfile(PublicProfile profile, DataCallback<Boolean> succses)
			throws DublicateEntryException, ClassNotFoundException, IOException {
		Number160 id = Number160.createHash(profile.getUsername());
		this.get(id, PublicProfile.class, (p) -> {
			if (p == null) {
				try {
					this.put(id, profile, succses);
				} catch (IOException e) {
					succses.onData(false);
					e.printStackTrace();
				}
			} else {
				succses.onData(false);
			}
		});
	}

	public void updateProfile(PublicProfile profile) throws IOException {
		this.put(Number160.createHash(profile.getUsername()), profile, success -> {});
	}
}
