package authoritymanager.server;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.StringTokenizer;
import java.util.Vector;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import tools.Authority;
import tools.DuplicatesSet;
import tools.ElementData;
import tools.UserLogin;


import authoritymanager.client.ClientConfiguration;
import authoritymanager.client.DataSerial;
import authoritymanager.client.DataPair;
import authoritymanager.client.Record;
import authoritymanager.client.SearchQuery;
import authoritymanager.client.SearchResults;
import authoritymanager.client.ServerService;
import authoritymanager.client.Utilities;

import com.google.gwt.user.server.rpc.RemoteServiceServlet;

public class ServerServiceImpl extends RemoteServiceServlet implements
		ServerService {
	public static String PATH		=	Authority.PATH  ; // use new File ...
	private static String DUMP_DIR	=	PATH + "dump" + File.separatorChar ;
	private static String SERVER_CONF	=	PATH + "EFG-server.conf" ;
	public final String LOGIN_FILE	=	PATH + "login.xml";

	private boolean initialized = false;

	//public Authority authorityPersons, authorityMovies, authorityCorporates;
	int idxPersons, idxMovies;
	public HashMap<String, DuplicatesSet> duplicates ;
	public Vector<UserLogin> users = new Vector<UserLogin>();
	private Random random = new Random(System.currentTimeMillis()) ;
	
	@Override
	public void init_server() {
		if (!initialized) {
			// System.out.println("GWTO module base: " + GWT.getModuleBaseURL())

			initialized = true;
			System.out.print("Initializing users ... ") ;
			initUsers();
			System.out.println("OK") ;
			
			// authorityPersons = TestModule.createAuthority("DIF-persons.conf",
			// "exp/dif-agent-p-", 326) ;
			// authorityMovies = TestModule.createAuthority("DIF-movies.conf",
			// "dif/dif_fw", 486) ;
			//authorityPersons = new Authority(ClientConfiguration.MOVIE_TAG, PATH + "EFG-persons.conf") ;
			//authorityMovies = new Authority(ClientConfiguration.MOVIE_TAG, PATH + "EFG-movies.conf") ;
			//authorityCorporates = new Authority(ClientConfiguration.CORPORATION_TAG, PATH + "EFG-corporates.conf") ;
			System.out.print("Loading configuration ... ") ;
			loadConfiguration() ;
			System.out.println("OK") ;
			
	//		duplicateMovies = getDuplicates(PATH + "movies-duplicates.txt");
	//		duplicatePersons = getDuplicates(PATH + "persons-duplicates.txt");
		}
	}

	public String getValue(Node node) {
		return node.getChildNodes().item(0).getNodeValue().trim() ;
	}
	
	private void loadConfiguration() {
		try {
			DocumentBuilderFactory docFactory = DocumentBuilderFactory
					.newInstance();
			DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
			Document doc = docBuilder.parse(SERVER_CONF);
			NodeList authorityList = doc.getDocumentElement().getElementsByTagName("authority");
			duplicates = new HashMap<String, DuplicatesSet>(3) ;
			for (int iAuthority = 0; iAuthority < authorityList.getLength(); iAuthority ++) {
				Node authorityNode = authorityList.item(iAuthority);
				if (authorityNode.getNodeType() == Element.ELEMENT_NODE) {
					Element element = (Element) authorityNode ;
					String authorityName = getValue(element.getElementsByTagName("name").item(0)) ;
					String configurationURL = getValue(element.getElementsByTagName("conf").item(0)) ;
					String duplicatesURL = getValue(element.getElementsByTagName("duplicates").item(0)) ;
					Authority authority = new Authority() ;
					authority.setType(authorityName) ;
					authority.setConfiguration(PATH + configurationURL) ;
					
					DuplicatesSet duplicatesSet = DuplicatesSet.fromFile(authorityName, PATH + duplicatesURL) ;
					duplicates.put(duplicatesSet.getType(), duplicatesSet) ;
				}
			}
		} catch (ParserConfigurationException e) {
			e.printStackTrace();
		} catch (SAXException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	private void initUsers() {
		try {
			DocumentBuilderFactory docFactory = DocumentBuilderFactory
					.newInstance();
			DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
			Document doc = docBuilder.parse(LOGIN_FILE);
			NodeList login = doc.getDocumentElement().getElementsByTagName(
					"user");
			for (int iNode = 0; iNode < login.getLength(); iNode++) {
				Node node = login.item(iNode);
				if (node.getNodeType() == Element.ELEMENT_NODE) {
					UserLogin userLogin = new UserLogin((Element) node);
					users.add(userLogin);
				}
			}
		} catch (ParserConfigurationException e) {
			e.printStackTrace();
		} catch (SAXException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	private Vector<DataPair> getDuplicates(String fileName) {
		try {
			BufferedReader in = new BufferedReader(new FileReader(new File(
					fileName)));

			String line;
			Vector<DataPair> results = new Vector<DataPair>();
			try {
				while ((line = in.readLine()) != null) {
					DataPair pair = new DataPair();
					DataSerial first = new DataSerial();
					double distance = Double.parseDouble(line);
					String fieldName, fieldValue;
					line = in.readLine();
					line = in.readLine();
					while (!line.equals("$")) {
						StringTokenizer st = new StringTokenizer(line, "\t");
						if (st.hasMoreTokens()) {
							fieldName = st.nextToken();
							if (st.hasMoreTokens())
								fieldValue = st.nextToken();
							else
								fieldValue = "";
							first.put(fieldName, fieldValue);
						}
						line = in.readLine();
					}
					DataSerial second = new DataSerial();
					line = in.readLine();
					while (!line.equals("$")) {
						StringTokenizer st = new StringTokenizer(line, "\t");
						if (st.hasMoreTokens()) {
							fieldName = st.nextToken();
							if (st.hasMoreTokens())
								fieldValue = st.nextToken();
							else
								fieldValue = "";
							second.put(fieldName, fieldValue);
						}
						line = in.readLine();
					}
					pair.addData(distance, first, second);
					results.add(pair);

					line = in.readLine();
				}
				idxPersons = 0;
				idxMovies = 0;
				return results;
			} catch (IOException e) {
				e.printStackTrace();
			}
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}
		return null;
	}

	
	
	@Override
	public void uploadFile() {
		System.out.println("0");
		HttpServletRequest request = super.getThreadLocalRequest();
		HttpServletResponse response = super.getThreadLocalResponse();
		System.out.println("1");
		boolean isMultipart = ServletFileUpload.isMultipartContent(request);
		List<FileItem> items;
		FileItemFactory factory = new DiskFileItemFactory();
		ServletFileUpload upload = new ServletFileUpload();
		System.out.println("2");
		try {
			items = upload.parseRequest(request);
			for (FileItem item : items) {
				System.out.println("item = " + item);
			}
		} catch (Exception e) {

		}
		// List items = null;
		String text = "not loaded!";

		if (isMultipart) {
			// Create a factory for disk-based file items
			// FileItemFactory factory = new DiskFileItemFactory();

			// Create a new file upload handler
			// ServletFileUpload upload = new ServletFileUpload(factory);

			// Parse the request
			try {
				// items = upload.parseRequest(request);
				// <===========================I get the exception here
			} catch (Exception e) {
				text = e.getStackTrace().toString();
			}
		}
	}

	@Override
	public void upload() {
		// TODO Auto-generated method stub

	}

	@Override
	public void fileUpload() {
		// TODO Auto-generated method stub

	}

	@Override
	public String getConfiguration() {
		/*
		 * try {
		 * 
		 * System.out.println("GetConfiguration"); JAXBContext jaxbContext =
		 * JAXBContext.newInstance("eu.europeanfilmgateway.efg"); Marshaller
		 * marshaller = jaxbContext.createMarshaller(); Unmarshaller
		 * unmarshaller = jaxbContext.createUnmarshaller(); ObjectFactory
		 * factory = new ObjectFactory();
		 * 
		 * Avcreation avCreation = factory.createAvcreation();
		 * 
		 * IdentifierType identifierType = factory.createIdentifierType();
		 * identifierType.setValue("dsfsD");
		 * avCreation.setIdentifier(identifierType);
		 * 
		 * IdentifyingType title = factory.createIdentifyingType();
		 * title.setLang("German"); avCreation.setIdentifyingTitle(title);
		 * 
		 * marshaller.marshal(avCreation, new File("test.xml"));
		 * 
		 * } catch (JAXBException e) { e.printStackTrace(); }
		 */
		DocumentBuilderFactory docFactory = DocumentBuilderFactory
				.newInstance();
		try {
			Document doc = docFactory.newDocumentBuilder().parse(
					new File(PATH + "efg.xsd"));
			doc.getDocumentElement().normalize();
			StringWriter writer = new StringWriter();
			Transformer transformer = TransformerFactory.newInstance()
					.newTransformer();
			transformer.transform(new DOMSource(doc), new StreamResult(writer));
			return writer.toString();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (SAXException e) {
			e.printStackTrace();
		} catch (ParserConfigurationException e) {
			e.printStackTrace();
		} catch (TransformerConfigurationException e) {
			e.printStackTrace();
		} catch (TransformerFactoryConfigurationError e) {
			e.printStackTrace();
		} catch (TransformerException e) {
			e.printStackTrace();
		}
		return null;
	}

	public String getStatus() {
		return getXML("status.xml");
	}

	public String getXML(String fileName) {
		try {
			DocumentBuilderFactory docFactory = DocumentBuilderFactory
					.newInstance();
			Document doc = docFactory.newDocumentBuilder().parse(
					new File(PATH + fileName));
			doc.getDocumentElement().normalize();
			StringWriter writer = new StringWriter();
			Transformer transformer = TransformerFactory.newInstance()
					.newTransformer();
			transformer.transform(new DOMSource(doc), new StreamResult(writer));
			return writer.toString();

		} catch (SAXException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (ParserConfigurationException e) {
			e.printStackTrace();
		} catch (TransformerConfigurationException e) {
			e.printStackTrace();
		} catch (TransformerFactoryConfigurationError e) {
			e.printStackTrace();
		} catch (TransformerException e) {
			e.printStackTrace();
		}
		return null;
	}

	@Override
	public void insert(String choice, String query) {
		System.out.println("Inserting: " + query);
		if (choice.equals(ClientConfiguration.MOVIE_TAG)) {
			String[] st = query.split("$");
			for (int i = 0; i < st.length; i++) {
				String[] fields = st[i].split(":");
				System.out.println(fields[0] + ":" + fields[1]);
			}
		}
		if (choice.equals(ClientConfiguration.PERSON_TAG)) {

		}
		if (choice.equals(ClientConfiguration.CORPORATION_TAG)) {

		}

	}

	@Override
	public boolean login(String username, String date, String password) {
		for (Iterator<UserLogin> it = users.iterator(); it.hasNext();) {
			UserLogin user = it.next();
			if (user.username.equals(username)) {
				// return BCrypt.checkpw(password, user.password) ;
				return password.equals(user.password);
			}
		}
		return false;
	}

	public void dumpUsers() {
		String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>" + "\n\n";

		xml += "<login>";
		for (Iterator<UserLogin> it = users.iterator(); it.hasNext();) {
			UserLogin user = it.next();
			xml += user.toXML();
		}
		xml += "</login>";

		try {
			PrintWriter writer = new PrintWriter(LOGIN_FILE);
			writer.println(xml);
			writer.flush();
			writer.close();
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	@Override
	public boolean register(String username, String password, String access) {
		for (Iterator<UserLogin> it = users.iterator(); it.hasNext();) {
			UserLogin user = it.next();
			if (user.username.equals(username)) {
				return false;
			} else {
				users.add(new UserLogin(username, password, access));
				dumpUsers();
				return true;
			}
		}
		users.add(new UserLogin(username, password, access));
		dumpUsers();
		return true;
	}

	@Override
	public String getPath() {
		File f = new File(PATH + "test.txt");
		String absPath = f.getAbsolutePath();
		System.out.println(absPath);
		String relPath = f.getPath();
		System.out.println(relPath);
		f.deleteOnExit();
		return absPath + "$" + relPath;
	}

	
	@Override
	public SearchResults search(String query) {
		System.out.println("Searching ..." + query) ;
		init_server();
		String searchType = query.substring(0, query.indexOf(Utilities.DELIMITER));
		query = query.substring(query.indexOf(Utilities.DELIMITER) + 1);

		Authority authority = Authority.byType(searchType) ;
		if (authority != null) {
			return new SearchResults(searchType, authority.search(query));
		}
		
		return null ;
		
	}

	@Override
	public SearchResults search(SearchQuery searchQuery) {
		System.out.println("Searching ..." + searchQuery) ;
		init_server();
		String searchType = searchQuery.getType() ;
		Authority authority = Authority.byType(searchType) ;
		if (authority != null) {
			return new SearchResults(searchType, authority.search(searchQuery));
		}
		return null ;
	}

	@Override
	public boolean addRecord(Record record) {
		// TODO Auto-generated method stub
		return false;
	}

	@Override
	public void merge(DataPair winner, DataPair loser) {
		// TODO Auto-generated method stub
		
	}

	
	@Override
	public void setDifferent(DataPair data) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public DataSerial getItem(String itemType, String itemID) {
		Authority authority = Authority.byType(itemType) ;
		if (authority != null) {
			return authority.getItem(itemID) ;
		}
		return null;
	}

	@Override
	public String getFile(String fileName) {
		try {
			BufferedReader reader = new BufferedReader(new FileReader(new File(PATH + fileName))) ;
			System.out.println("reader.toString: " + reader.toString()) ;
			String line ;
			String result = "" ;
			while ((line = reader.readLine()) != null) {
				result += line + "\n" ;
			}
			return result ;
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return null;
	}

	public char int2hex(int n) {
		if (n < 10) {
			return (char)('0' + n) ;
		}
		else {
			return (char) ('A' + (n - 10)) ;
		}
	}
	public String genID(int length) {
		String res = "" ;
		
		for (int i = 0 ; i < length ; i ++) {
			res += int2hex(random.nextInt(16)) ;
		}
		return res ;
	}
	@Override
	public String createID(String itemType) {
		String id = "EFG:" + ClientConfiguration.getIDTag(itemType) + "_" + genID(16) ;
		return id ;
	}

	@Override
	public boolean addRecord(DataSerial dataSerial) {
		System.out.println("Adding: " + dataSerial) ;
		Authority authority = Authority.byType(dataSerial.getType()) ;
		ElementData elementData = ElementData.fromDataSerial(authority, dataSerial) ;
		authority.addElement(elementData) ;
		System.out.println(elementData.toXML()) ;
		return true;
	}

	@Override
	public DataSerial getItem(String itemID) {
		long curTime = System.currentTimeMillis() ;
		ElementData data = Authority.getByID(itemID) ;
		if (data != null) {
			DataSerial result = data.toDataSerial() ;
			System.out.println("getItem(" + itemID + ") = " + (System.currentTimeMillis() - curTime)) ;
			return result ;
		}
		return null;
	}

	@Override
	public String getNextCandidate(String pageType) {
		long curTime = System.currentTimeMillis() ;
		DuplicatesSet duplicatesSet = duplicates.get(pageType) ;
		if (duplicatesSet != null) {
			String result = duplicatesSet.getNext() ;
			System.out.println("getNextCandidate(" + pageType + ") = " + (System.currentTimeMillis() - curTime)) ;
			return result ;
		}
		return null;
	}

	@Override
	public String getPrevCandidate(String pageType) {
		long curTime = System.currentTimeMillis() ;
		DuplicatesSet duplicatesSet = duplicates.get(pageType) ;
		if (duplicatesSet != null) {
			String result = duplicatesSet.getPrev() ;
			System.out.println("getPrevCandidate(" + pageType + ") = " + (System.currentTimeMillis() - curTime)) ;
			return result ;
		}
		return null;
	}

	@Override
	public String getServerConfiguration() {
		Collection<Authority> authorities = Authority.getAuthorities() ;
		String configuration = "" ;
		for (Iterator<Authority> itAuthority = authorities.iterator() ; itAuthority.hasNext() ; ) {
			Authority authority = itAuthority.next() ;
			String authorityType = authority.getType() ;
			boolean isLoaded = authority.isLoaded() ;
			boolean isChanged = authority.isChanged() ;
			configuration += authority.getType() + "\t" + isLoaded + "\t" + isChanged + "\t" +
							true + "\n" ;
			
		}
		return configuration ;
	}

	@Override
	public void loadAuthority(String authType) {
		Authority authority = Authority.byType(authType) ;
		if (!authority.isLoaded()) {
			authority.loadData() ;
		}
		
	}

	@Override
	public void loadDuplicates(String authType) {
		DuplicatesSet duplicatesSet = duplicates.get(authType) ;
		if (duplicatesSet != null) {
			
		}
		
	}


	@Override
	public void dumpAuthority(String authType) {
		Authority authority = Authority.byType(authType) ;
		String currentDateDir = new SimpleDateFormat("ddMMyyyy_hhmmss").format(Calendar.getInstance().getTime()) ;
		String dumpDir = DUMP_DIR + File.separatorChar + currentDateDir ;
		File fDir = new File(dumpDir) ;
		fDir.mkdir() ;
		authority.dump(dumpDir + File.separatorChar) ;
	}

}
