/*
 * Created on 20-Jan-2005
 */
package org.bodington.servlet.facilities;

import java.awt.Color;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.URLEncoder;
import java.text.DecimalFormat;
import java.util.Calendar;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.params.HttpConnectionManagerParams;
import org.apache.log4j.Logger;
import org.bodington.server.BuildingServer;
import org.bodington.server.BuildingServerException;
import org.bodington.server.BuildingServerUserException;
import org.bodington.servlet.EscapedHtmlWriter;
import org.bodington.servlet.Request;
import org.bodington.util.BodingtonURL;


/**
 * @author buckett
 */
public class Utils
{
    
    private static Logger log = Logger.getLogger(Utils.class.getName());

    private static final Map days = new TreeMap();
    private static final Map hours = new TreeMap();
    private static final Map minutes = new TreeMap();
    private static final String[] monthNames = {"January", "Febuary", "March", "April", "May", "June", "July", "August", "September", "October", "November", "Decemeber"};
    private static final Map months = new TreeMap();
    
    private static HttpClient httpClient;
    
    private static DecimalFormat formatBytes = new DecimalFormat("###B");
    private static DecimalFormat formatKiloBytes = new DecimalFormat("##0.0kB");
    private static DecimalFormat formatMegaBytes = new DecimalFormat("##0.0MB");
    
    static {
    	for(int count = 0; count < 24; count++) {
    		final Integer hour = new Integer(count);
    		hours.put(hour, hour);
    	}
    	for(int count = 0; count < 60; count++) {
    		final Integer minute = new Integer(count);
    		minutes.put(minute, minute);
    	}
    	for(int count = 1; count< 32; count++) {
    		final Integer day = new Integer(count);
    		days.put(day,day);
    	}
    	for(int count = 0; count < 12; count++) {
    		final Integer month = new Integer(count);
    		months.put(month, monthNames[count]);
    		
    	}

    	MultiThreadedHttpConnectionManager manager = new MultiThreadedHttpConnectionManager();
    	HttpConnectionManagerParams httpManagerParams = new HttpConnectionManagerParams();
    	
    	httpManagerParams.setDefaultMaxConnectionsPerHost(10);
    	httpManagerParams.setConnectionTimeout(5000);
    	httpManagerParams.setSoTimeout(10000);
    	
    	manager.setParams(httpManagerParams);
        
        httpClient = new HttpClient(manager);
    	
    }

    /**
     * Attempt to convert the calendar data in the request into a calendar object.
     * If the conversion fails then null is returned.
     * @param req The servlets request to get the raw data from.
     * @param prefix The prefix that the form elements used.
     * @return The converted calendar object.
     */
    public static Calendar parseCalendarChooser(Request req, String prefix)
    {
    	Calendar calendar = Calendar.getInstance();
    	try
    {
    		calendar.set(
    			Integer.parseInt(req.getParameter(prefix+ ".year")),
    		Integer.parseInt(req.getParameter(prefix+ ".month")),
    		Integer.parseInt(req.getParameter(prefix+ ".day")),
    		Integer.parseInt(req.getParameter(prefix+ ".hour")),
    		Integer.parseInt(req.getParameter(prefix+ ".minute"))
    		
    		);
    }
    	catch (NumberFormatException nfe)
    {
    		return null;
    }
    	return calendar;
    		
    }

    public static void writeCalendarChooser(PrintWriter out, String prefix)
    {
    	writeCalendarChooser(out, prefix, Calendar.getInstance());
    }

    /**
     * Outputs a date and time chooser to the page. If no default calendar is supplied
     * the currently time/date is used. The really should be in a template which can then
     * be included into other pages so the changing to format of the calendar can be done without
     * having to hack the Java.
     * @param out Where all the HTML is written to.
     * @param prefix The prefix for all the calendar form elements to allow multiple calendars on a page.
     * @param calendar The value to set the calendar chooser to.
     * @see #writeSelect(PrintWriter, String, Map, Object)
     */
    public static void writeCalendarChooser(PrintWriter out, String prefix, Calendar calendar)
    {
    	if (calendar == null) {
    		calendar = Calendar.getInstance();
    	}
    	out.write("Time: ");
    	writeSelect(out, prefix+".hour", hours, new Integer(calendar.get(Calendar.HOUR_OF_DAY)));
    	writeSelect(out, prefix+".minute", minutes, new Integer(calendar.get(Calendar.MINUTE)));
    	out.write("<br/>");
    	out.write("Date: ");
    	writeSelect(out, prefix+".day", days, new Integer(calendar.get(Calendar.DAY_OF_MONTH)));
    	writeSelect(out, prefix+".month", months, new Integer(calendar.get(Calendar.MONTH)));
    	out.write("<input type=\"text\" name=\""+ prefix + ".year\" value=\""+ calendar.get(Calendar.YEAR)+ "\" size=\"4\"/>");
    	
    }

    /**
     * Outputs an HTML &lt;select&gt; tag with the values supplied. No sensible checking is 
     * done at the moment. The Map supplied to this function probably wants to be a
     *  SortedMap so that the items are displayed in a sensible manner. 
     * @param out Where all the HTML output is written.
     * @param name The name associated with this form element.
     * @param map A Map containing the values to be outputed. 
     * @param selected The key in the Map that should be selected.
     */
    public static void writeSelect (PrintWriter out, String name, Map map, Object selected)
    {
    	out.write("<select name=\""+ name+ "\">\n");
    	Set entries = map.entrySet();
    	Iterator entriesIt = entries.iterator();
    	while (entriesIt.hasNext())
    	{
    		final Map.Entry entry =(Map.Entry)entriesIt.next();
    		out.write("<option value=\""+ entry.getKey() +"\"");
    		if (entry.getKey().equals(selected))
    		{
    			out.write(" selected");
    		}
    		if (entry.getValue() == null) 
    		{
    			out.write("/>\n");
    		}
    		else 
    		{
    			out.write(">"+ entry.getValue()+ "</option>\n");
    		}
    	}
    	// Finish the select
    	out.write("</select>\n");		
    }
    
    /**
     * Check to see if a username should be logging in using WebAuth.
     * The username of <code>sysadmin</code> is never forced through WebAuth.
     * @param username The username to check.
     * @return True if the user should be doing WebAuth.
     * @throws BuildingServerException If something went wrong with the check.
     */
    public static boolean checkWebAuthUser(String username) throws BuildingServerException
    { 
        BufferedReader in = null;
        String url = null;
        String line = null;
        HttpMethod method = null;
        
        if (username.equals("sysadmin")) return false;
        
        try
        {
            url = new String(BuildingServer.getInstance().getProperties()
                .getProperty("bodington.webauth.url",
                    "http://admin.herald.ox.ac.uk/weblearn_status")
                + "?username=" + URLEncoder.encode(username, "UTF-8"));
            log.debug("Starting checking: "+ username);
            method = new GetMethod(url);
            httpClient.executeMethod(method);

            if (method.getStatusCode() != HttpStatus.SC_OK)
            {
                throw new BuildingServerException("WA check got bad result: "
                    + method.getStatusCode());
            }
            in = new BufferedReader(new InputStreamReader(method
                .getResponseBodyAsStream()));
            while (true)
            {
                line = in.readLine();
                if (line == null)
                {
                    break;
                }
                if (line.startsWith("Status: "))
                {
                    line = line.substring(8);
                    if (line.equals("ok"))
                        return true;
                    else if (line.equals("expired"))
                        return false;
                    else if (line.equals("not found"))
                        return false;
                    else if (line.equals("bad request")) return false;
                }

            }
        }
        catch (IllegalArgumentException e)
        {
            log.warn("Invalid URL: " + url);
            throw new BuildingServerException(e.toString());
        }
        catch (IOException e)
        {
            log.warn("IO problem");
            throw new BuildingServerException(e.toString());
        }
        finally
        {
            log.debug("Finished checking: "+ username);
            if (method != null)
            {
                method.releaseConnection();
            }
            
            if (in != null)
            {
                try
                {
                    in.close();
                }
                catch (IOException ioe)
                {
                    ;
                }
            }
            
        }

        return false;
    }
    
    public static String toCSSColor(Color color)
    {
        return "#"+ Integer.toHexString( color.getRGB() ).substring( 2 );
    }
    
    /**
     * The writes out a &lt;base&gt; tag for the current resource.
     * This is currently used in the MCQ debrief for making sure image links still work.
     * @param request The servlet request from which we get the resource.
     * @param out Where we write the tag.
     */
    public static void writeBase(Request request, PrintWriter out)
    {
        BodingtonURL url = new BodingtonURL(request);
        out.println("<base href=\""+ url.getResourceUrl(request.getResource())+ "\"/>");
    }
    
    /**
     * Writes out the Exception in HTML
     * @param bsue The Exception to format.
     * @param out The Writer to send the HTML to.
     */
    public static void writeUserExceptionHTML(BuildingServerUserException bsue, PrintWriter out)
    {
        out.println("<div class='errors'>");
        out.println("<span class='message'>");
        out.println(bsue.getMessage());
        out.println("</span>");
        Iterator messages = bsue.iterator();
        if (messages.hasNext())
        {
            out.println("<ul>");
            EscapedHtmlWriter escapedOut = new EscapedHtmlWriter(out);
            while(messages.hasNext())
            {
                out.print("<li>");
                escapedOut.print((String)messages.next());
                out.println("</li>");
            }
            out.println("</ul>");
        }
        out.println("</div>");
    }
    
    public static void writeError(String error, PrintWriter out)
    {
        out.println("<div class='errors'>");
        out.println("<ul><li>");
        out.println(error);
        out.println("</li></ul>");
        out.println("</div>");
    }
    
    public static void writeMessage(String message, PrintWriter out)
    {
        out.println("<div class='message'>");
        out.println("<ul><li>");
        out.println(message);
        out.println("</li></ul>");
        out.println("</div>");
    }
    
    /**
     * Writes out the Exception in text.
     * @param bsue The Exception to format.
     * @param out The Writer to send the text to.
     */
    public static void writeUserExceptionRaw(BuildingServerUserException bsue, PrintWriter out)
    {
        out.println(bsue.getMessage());
        Iterator messages = bsue.iterator();
        EscapedHtmlWriter escapedOut = new EscapedHtmlWriter(out);
        while(messages.hasNext())
        {
            out.print(" - ");
            escapedOut.print((String)messages.next());
            out.println();
        }
        
    }
    
    /**
     * Quickly create a map from a 2D array.
     */
    public static Map createMap(Object[][] data)
    {
        Map map = new TreeMap();
        for (int i = 0; i < data.length; i++)
            map.put(data[i][0], data[i][1]);
        return map;
    }
    
    /**
     * Formats file sizes in a more human reabable form.
     * @param size The size of the file in bytes.
     * @return A human readable file size.
     */
    public static String toHumanReadableSize(long size)
    {
        if (size < 1000)
            return formatBytes.format(size);
        if (size < 1000000)
            return formatKiloBytes.format(((double)size)/1000.0);
        return formatMegaBytes.format(((double)size)/1000000.0);
    }

}
