/**
 * Copyright 2009-2012 OpenAIRE PROJECT (Bielefeld University)
 * Original author: Marek Imialek <marek.imialek at uni-bielefeld.de>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package eu.dnetlib.data.udm.servlet;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.zip.GZIPOutputStream;

import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.log4j.Logger;
import org.semanticdesktop.aperture.mime.identifier.magic.MagicMimeTypeIdentifier;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;

import eu.dnetlib.common.utils.MimeType;
import eu.dnetlib.data.udm.utils.DateUtils;

/**
 * The HttpProvideServlet delivers usage data for specified date or dates range.
 * The servlet supports the following types of requests:
 * 
 * - services/HttpServletProvider/dayPackage?date=YYYY-MM-DD
 * - services/HttpServletProvider/rangePackage?dateFrom=YYYY-MM-DD&dateUntil=YYYY-MM-DD
 * 
 * The first request delivers XML file containing usage data collected by harvester 
 * between the current day and the previous day. The file is delivered as a gzip-compressed
 * package.
 * 
 * The second request delivers XML file containing usage data for specified 
 * date range. The file is delivered as a gzip-compressed package.
 * 
 * @author <a href="mailto:marek.imialek at uni-bielefeld.de">Marek Imialek</a>
 */
public class HttpProvideServlet extends HttpServlet {
    
    /** The Constant serialVersionUID. */
    private static final long serialVersionUID = 123455787912873234L;

    /** The Constant log. */
	protected static final Logger log = Logger.getLogger(HttpProvideServlet.class);

	/** The Constant REQUEST_DATE_PARAM. */
	private static final String REQUEST_DATE_PARAM = "date";
	
	/** The Constant REQUEST_DATE_FROM_PARAM. */
	private static final String REQUEST_DATE_FROM_PARAM = "dateFrom";

	/** The Constant REQUEST_DATE_UNTIL_PARAM. */
	private static final String REQUEST_DATE_UNTIL_PARAM = "dateUntil";
	
	/** The Constant COMPRESSED_USAGE_DATA_PACKAGE_PREFIX_NAME. */
	private static final String COMPRESSED_USAGE_DATA_PACKAGE_PREFIX_NAME = "udp-";

	/** The Constant COMPRESSED_USAGE_DATA_PACKAGE_POSTFIX_NAME. */
	private static final String COMPRESSED_USAGE_DATA_PACKAGE_POSTFIX_NAME = ".xml.gz";
	
	/** The Constant NON_COMPRESSED_USAGE_DATA_PACKAGE_POSTFIX_NAME. */
	private static final String NON_COMPRESSED_USAGE_DATA_PACKAGE_POSTFIX_NAME = ".xml";

	/** The mime type identifier. */
    private static MagicMimeTypeIdentifier mimeTypeIdentifier = 
    	new MagicMimeTypeIdentifier();
    
	/* (non-Javadoc)
	 * @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest, 
	 * javax.servlet.http.HttpServletResponse)
	 */
	@Override
	protected void doGet(HttpServletRequest request, HttpServletResponse resp)
		throws ServletException, IOException {
		
		try {
			log.debug("Request: "+ request.getRequestURI()+"?"+request.getQueryString());
			
			if (request.getPathInfo() == null ) {
				String msg = "Request method not specified!";
				log.error(msg);
				resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
    			return;
    		}
			
			HttpProvideServletProp servletProperties = 
				getBean("HttpServletProperties", HttpProvideServletProp.class);
    		
			String directory = servletProperties.getHttpServletSrcDirectory();
			
			if (directory == null || "".equals(directory)) {
				String msg = "Main directory not specified in service properties file!";
				log.error(msg);
				resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, msg);
    			return;
    		}
						
			if (request.getPathInfo().equals("/dayPackage")) { 
				if (dayPackage(request, resp, directory))
					resp.setStatus(HttpServletResponse.SC_OK);
			} else if (request.getPathInfo().equals("/rangePackage")) {
				if(rangePackage(request, resp, directory))
					resp.setStatus(HttpServletResponse.SC_OK);
			} else { 
				String msg = "No proper request!";
				log.error(msg);
				resp.sendError(HttpServletResponse.SC_BAD_GATEWAY, msg);
			}
			
        } catch (Exception e) {
        	log.error(e.getMessage());
        	log(e.getMessage(), e);
            resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
        }  				
			
	}
	
	/**
	 * Day package.
	 * 
	 * @param request the request
	 * @param resp the resp
	 * @param directory the directory
	 * 
	 * @return true, if successful
	 * 
	 * @throws Exception the exception
	 */
	public boolean dayPackage(HttpServletRequest request, HttpServletResponse resp, 
			String directory) throws Exception {
		
		String dateParam = request.getParameter(REQUEST_DATE_PARAM); 
		
		if (dateParam == null ) {
			String msg = "Date parameter not specified!";
			log.error(msg);
			resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
			return false;
		}
		
		log.debug("Day package requested for: " + dateParam);
			
		if( !DateUtils.verifyDate(dateParam, resp)) {
			String msg = "Date format parameter not correct, " +
				"excepcted YYYY-MM-DD !";
			log.error(msg);
			resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
			return false;
		}
			
		String year = dateParam.substring(0,dateParam.indexOf("-"));
		String month = dateParam.substring(dateParam.indexOf("-")+1, 
				dateParam.lastIndexOf("-"));
		
		
		String objectPath = directory+"/"+year+"/"+month+"/"+dateParam +
			NON_COMPRESSED_USAGE_DATA_PACKAGE_POSTFIX_NAME; 
		
		File objectPathFile = new File (objectPath);
		
		if (!objectPathFile.exists()) {
			String msg = "Can not find usage data object for specified date ";
			log.error(msg + " (" + objectPathFile.getAbsolutePath()+")");
			resp.sendError(HttpServletResponse.SC_NOT_FOUND, msg);
			return false;
		}
		
		servletCompressedResponse(resp, 
			new ArrayList<String>(Arrays.asList(objectPath)),
				getPackageName(dateParam, dateParam));
		
		return true;
	}
	
	/**
	 * Range package.
	 * 
	 * @param request the request
	 * @param resp the resp
	 * @param directory the directory
	 * 
	 * @return true, if successful
	 * 
	 * @throws IOException Signals that an I/O exception has occurred.
	 * @throws Exception the exception
	 */
	public boolean rangePackage(HttpServletRequest request, HttpServletResponse resp, 
			String directory) throws IOException, Exception {
		
		String dateFrom = request.getParameter(REQUEST_DATE_FROM_PARAM);
		String dateUntil = request.getParameter(REQUEST_DATE_UNTIL_PARAM);
		
		if (dateFrom == null ) {
			String msg = "Parameter dateFrom not specified!";
			log.error(msg);
			resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
			return false;
		}
		
		if( !DateUtils.verifyDate(dateFrom, resp)){
			String msg = "Date format parameter not correct, " +
					"excepcted YYYY-MM-DD !";
			log.error(msg);
			resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
			return false;
		}
		
		if (dateUntil == null ) {
			dateUntil = DateUtils.getCurrentDate();
		} else 
			if( !DateUtils.verifyDate(dateUntil, resp)) {
				String msg = "Date format parameter not correct, " +
					"excepcted YYYY-MM-DD !";
				log.error(msg);
				resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
				return false;
			}

		if (!DateUtils.compareDates(dateFrom, dateUntil)) {
			String msg = "DateFrom parameter can not be greater then" +
					" dateUntil parameter!";
			log.error(msg);
			resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
			return false;
		}
		
		log.debug("Range package requested for: " + dateFrom + " - "+ dateUntil);
		
		List<String> objectsList = 
			findRangeObjects(dateFrom,dateUntil, directory);
		
		if (objectsList == null | objectsList.size() == 0) {
			String msg = "Can not find usage data " +
					"objects for specified date range!";
			log.error(msg);
			resp.sendError(HttpServletResponse.SC_NOT_FOUND, msg);
			return false;
		}
		
		log.debug("Found "+ objectsList.size() +" objects");
		
		servletCompressedResponse(resp,objectsList,
				getPackageName(dateFrom, dateUntil));
		
		return true;
	}
	
	/**
	 * Find range objects.
	 * 
	 * @param dateFrom the date from
	 * @param dateUntil the date until
	 * @param directory the directory
	 * 
	 * @return the list< string>
	 * 
	 * @throws Exception the exception
	 */
	private List<String> findRangeObjects(String dateFrom, String dateUntil,
			String directory) throws Exception {
		
		List<String> objectsPaths = new ArrayList<String>();
		
		int minYear = Integer.valueOf(dateFrom.substring(0,dateFrom.indexOf("-")));
		int minMonth = Integer.valueOf(dateFrom.substring(dateFrom.indexOf("-")+1, 
				dateFrom.lastIndexOf("-")));
		int dayFrom = Integer.valueOf( dateFrom.substring( dateFrom.lastIndexOf("-")+1,
				dateFrom.length()));
		int minDay = 0;
		if (dayFrom<10)
			minDay = Integer.valueOf(minMonth + "0" + dayFrom );
		else
			minDay = Integer.valueOf(minMonth +""+ dayFrom );
		
		int maxYear = Integer.valueOf(dateUntil.substring(0,dateUntil.indexOf("-")));
		int maxMonth = Integer.valueOf(dateUntil.substring(dateUntil.indexOf("-")+1, 
				dateUntil.lastIndexOf("-")));
		int dayUntil = Integer.valueOf(dateUntil.substring( dateUntil.lastIndexOf("-")+1,
				dateUntil.length()));
		int maxDay = 0;
		if (dayUntil<10)
			maxDay = Integer.valueOf(maxMonth + "0" + dayUntil);
		else
			maxDay = Integer.valueOf(maxMonth +""+ dayUntil);
		
		File mainDir = new File (directory);
		String[] yearDirList = mainDir.list();
	
		for (int i=0; i<yearDirList.length; i++) {
			int year = Integer.valueOf(yearDirList[i]);
			
			if ( year >= minYear && year <= maxYear) {
				//log.debug("Year:" + yearDirList[i]);
				
				File yearDir = new File (directory+"/"+yearDirList[i]);
				String[] monthDirList = yearDir.list();
			
				for (int m=0; m<monthDirList.length; m++) {
					int month = Integer.valueOf(monthDirList[m]);
					//log.debug("Month:" + month);
					if ( month >= minMonth && month <= maxMonth) {
						//log.debug("IN-Month:" + month);
						String monthDirPath = directory+"/"
							+yearDirList[i]+"/"+monthDirList[m];
						File monthDir = new File (monthDirPath);
						String[] dayDirList = monthDir.list();
						
						for (int d=0; d<dayDirList.length; d++) {
							if (dayDirList[d].contains(".lck"))
								continue;

							int day = Integer.valueOf(dayDirList[d].substring( 
									dayDirList[d].lastIndexOf("-")+1,
									dayDirList[d].lastIndexOf(".")));
							if (day < 10)
								day = Integer.valueOf(month+"0"+day);
							else
								day = Integer.valueOf(month+""+day);
							//log.debug("Day:" + day +" ("+minDay+"-"+maxDay+")");
							if (day >= minDay & day <= maxDay) {
								objectsPaths.add(monthDirPath+"/"+dayDirList[d]);
								log.debug("File:"+ dayDirList[d]);
							}
						}	
					}
				}
			}
		}
		
		return objectsPaths;
	}

	/**
	 * Servlet standard response.
	 * 
	 * @param resp the resp
	 * @param objectPath the object path
	 * @param fileSize the file size
	 * 
	 * @throws Exception the exception
	 */
	@SuppressWarnings("unused")
	private void servletStandardResponse (HttpServletResponse resp, 
			String objectPath, int fileSize ) throws Exception {
		
		log.debug("Creating standard response: file:" + objectPath);
		FileInputStream is = new FileInputStream(objectPath);
		ServletOutputStream os = resp.getOutputStream();
		
		resp.setContentLength(fileSize);
		resp.setContentType(MimeType.identify(
				null, new FileInputStream(objectPath), 
				mimeTypeIdentifier));
		try {
    		byte[] buf = new byte[1024];
			int ByteRead, ByteWritten = 0;
			while ((ByteRead = is.read(buf)) != -1) {
				os.write(buf, 0, ByteRead);
				ByteWritten += ByteRead;
				os.flush();
			}
			
			if (fileSize != ByteWritten) 
				log.warn("File has not been completly written, bytes expected: "
						+fileSize + "  bytes written: "+ ByteWritten);
		} catch (Exception e) {
			log.error("Exception by creating servlet Response: " +e);	
		} finally {
			if (is != null)
				is.close();
			if (os != null)
				os.close();
		}    
		
	}
	
	/**
	 * Servlet compressed response.
	 * 
	 * @param resp the resp
	 * @param objectPaths the object paths
	 * @param packageName the package name
	 * 
	 * @throws Exception the exception
	 */
	private void servletCompressedResponse (HttpServletResponse resp,
			List<String> objectPaths, String packageName ) throws Exception {

		log.debug("Creating compressed response for "+objectPaths.size()+" files");
		
		GZIPOutputStream out = new GZIPOutputStream(resp.getOutputStream());
		Iterator<String> iter = objectPaths.iterator();

		resp.setContentType("application/gzip");
		resp.addHeader("Content-Disposition", 
				"attachment; filename=\""+packageName+"\"");
		resp.addHeader("Content-Transfer-Encoding", "binary");
		String fileHeader = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
				"<ListRecords>\n";
		String fileFooter = "</ListRecords>"; 

		try { 	
			out.write(fileHeader.getBytes());
			while (iter.hasNext()) {
				File file = new File (iter.next());
				InputStream in = new FileInputStream(file);
				
				byte[] buf = new byte[1024];
				int ByteRead = 0;
				while ((ByteRead = in.read(buf)) != -1) {
					out.write(buf, 0, ByteRead);
					out.flush();
				}
				in.close();
			}
			out.write(fileFooter.getBytes());

		} catch (Exception e) {
			log.error("Exception by compressing servlet Response: " +e);
		} finally {
			if (out != null)
				out.close();
		}    
	}

	/**
	 * Gets the package name.
	 * 
	 * @param dateFrom the date from
	 * @param dateUntil the date until
	 * 
	 * @return the package name
	 */
	private String getPackageName (String dateFrom, String dateUntil) {
		 return COMPRESSED_USAGE_DATA_PACKAGE_PREFIX_NAME 
		 	+ dateFrom.replace("-", "") 
		 	+"-" +dateUntil.replace("-", "")
		 	+COMPRESSED_USAGE_DATA_PACKAGE_POSTFIX_NAME;
	}
	
	 /**
 	 * Gets the bean.
 	 * 
 	 * @param name the name
 	 * @param type the type
 	 * 
 	 * @return the bean
 	 */
 	protected <T> T getBean(String name, Class<T> type) {
	     WebApplicationContext ctx = 
             WebApplicationContextUtils.getRequiredWebApplicationContext(
            		 getServletContext());
	     @SuppressWarnings("unchecked")
	     T result = (T)ctx.getBean(name, type); 
	     return result;
	 }

}