/*
 * @(#)HttpView.java	1.3 04/01/27
 *
 * Copyright (c) 1999-2004 Sun Microsystems, Inc. All rights reserved. 
 * PROPRIETARY/CONFIDENTIAL
 * Use is subject to license terms
 */

package example.http;

import javax.microedition.midlet.*;
import javax.microedition.io.*;
import javax.microedition.lcdui.*;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.DataInputStream;
import java.util.Vector;
import java.util.Date;

import java.lang.String;
import example.About;

/**
 * An example MIDlet to fetch a page using an HttpConnection.
 * Refer to the startApp, pauseApp, and destroyApp
 * methods so see how it handles each requested transition.
 */
public class HttpView extends MIDlet implements CommandListener, Runnable {

    /** user interface command for indicating Exit request. */
    Command exitCommand  = new Command("Exit", Command.EXIT, 2);
    /** user interface comand for indicating a page reload request. */
    Command reloadCommand = new Command("Reload", Command.SCREEN, 1);
    /** user interface command to request an HTTP HEAD transaction. */
    Command headCommand = new Command("Head", Command.SCREEN, 1);
    /** user interface command to request an HTTP POST transaction. */
    Command postCommand = new Command("Post", Command.SCREEN, 1);
    /** user interfrace command to request an HTTP GET transaction. */
    Command getCommand = new Command("Get", Command.SCREEN, 1);
    /** user interface command to request copyright information. */
    Command aboutCommand = new Command("About", Command.HELP, 1);
    /** user interface command to cancel the current screen. */
    Command cancelCommand = new Command("Cancel", Command.SCREEN, 1);
    /** user interface command to return back to previous screen. */
    Command backCommand = new Command("Back", Command.BACK, 1);
    /** user interface command to request current HTTP headers. */
    Command headersCommand = new Command("Headers", Command.SCREEN, 1);
    /** user interface command to display current HTTP request headers. */
    Command requestsCommand = new Command("Requests", Command.SCREEN, 1);
    /** user interface command to display errors from current request. */
    Command errorsCommand = new Command("Errors", Command.SCREEN, 1);
    /** user interface command to enter a new URL */
    Command newURLCommand = new Command("New URL", Command.SCREEN, 10);
    /** user inetrface command to remove the current URL */
    Command removeURLCommand = new Command("Remove", Command.SCREEN, 11);
    /** user interface command to confirm current screen. */
    Command okCommand = new Command("Ok", Command.SCREEN, 1);
    /** user interface command to display help message. */
    Command helpCommand = new Command("Help", Command.HELP, 1);
    /** user interface component containing a list of URLs */
    List urlList;
    /** array of current URLs */
    Vector urls;
    /** user interface alert component. */
    Alert alert;
    /** user interface text box for the contents of the fetched URL. */
    TextBox content;
    /** current display. */
    Display display;
    /** instance of a thread for asynchronous networking and user interface. */
    Thread thread;
    /** current requested url. */
    String url;			
    /** current HTTP request type - GET, HEAD, or POST */
    Command requestCommand;	
    /** user interface form to hold progress results. */
    Form progressForm;
    /** user interface progress indicator. */
    Gauge progressGauge;
    /** user interface screen for HTTP headers */
    Form headerForm;
    /** form to display request including parsing */
    Form requestForm;          
    /** form to display exceptions */
    Form errorsForm;		
    /** data entry text box for inputting URLs */
    TextBox urlbox;

    /** initialize the MIDlet with the current display object. */
    public HttpView() {
        display = Display.getDisplay(this);
        setupList();
        alert = new Alert("Warning");
        alert.setTimeout(2000);

        headerForm = new Form("Headers");
        headerForm.addCommand(backCommand);
        headerForm.addCommand(requestsCommand);
        headerForm.setCommandListener(this);

        requestForm = new Form("Request headers");
        requestForm.addCommand(backCommand);
        requestForm.addCommand(errorsCommand);
        requestForm.setCommandListener(this);

        progressForm = new Form("Progress");
        progressForm.addCommand(cancelCommand);
        progressForm.setCommandListener(this);

        progressGauge = new javax.microedition.lcdui.Gauge(url, false, 9, 0);
        progressForm.append(progressGauge);

        errorsForm = new Form("Errors");
        errorsForm.addCommand(backCommand);
        errorsForm.addCommand(headersCommand);
        errorsForm.setCommandListener(this);
        
        urlbox = new TextBox("Enter Url", "http://", 400, TextField.URL);
        urlbox.addCommand(okCommand);
        urlbox.setCommandListener(this);
    }

    /**
     * Start creates the thread to do the timing.
     * It should return immediately to keep the dispatcher
     * from hanging.
     */
    public void startApp() {
	/* Bytes read from the URL update connection. */
	int count;
	/* Check for inbound async connection for sample Finger port. */
	String[] connections = PushRegistry.listConnections(true);

	/* HttpView was started to handle inbound request. */
	String pushProperty = getAppProperty("MIDlet-Push-1");

	if (connections != null && connections.length > 0) {
	    String newurl = "Pushed URL Placeholder";

	    /* DEBUG: Test basic get registry information interfaces. */
	    try {
		String midlet = PushRegistry.getMIDlet(connections[0]);
		String filter = PushRegistry.getFilter(connections[0]);

	    } catch (Exception e) {
		e.printStackTrace();
	    }
		    
	    /* Check for socket or datagram connection. */
	    if (connections[0].startsWith("socket://")) {
		try {
		    /* Simple test assumes a server socket connection. */
		    ServerSocketConnection scn = (ServerSocketConnection)
			Connector.open(connections[0]);
		    SocketConnection sc = (SocketConnection)
			scn.acceptAndOpen();
		    
		    /* Read one line of text as a new URL to add to the list. */
		    DataInputStream dis = sc.openDataInputStream();
		    byte[] buf = new byte[256];
		    int endofline = 0;
		    count =	dis.read(buf);
		    for (int i = 0; i < count; i++) {
			if (buf[i] == '\n') {
			    endofline = i;
			    break;
			}
		    }
		    newurl = new String(buf, 0, endofline);

		    dis.close();

		    sc.close();
		    scn.close();
		} catch (IOException e) {
		    e.printStackTrace();
		}
		
		/* 
		 * After successfully receiving a socket posted URL
		 * register a datgram connection, too.
		 */
		try {
		    PushRegistry.registerConnection("datagram://:40080",
						    "example.http.HttpView",
						    "129.148.*.*");
		} catch (ClassNotFoundException e) {
		    e.printStackTrace();
		} catch (IllegalArgumentException e) {
		    e.printStackTrace();
		} catch (ConnectionNotFoundException e) {
		    e.printStackTrace();
		} catch (IOException e) {
		    e.printStackTrace();
		}
		
	    } else if (connections[0].startsWith("datagram://")) {
		/* Must be a datagram connection. */
		try {
		    UDPDatagramConnection udc = (UDPDatagramConnection)
			Connector.open(connections[0]);
		    Datagram dg = udc.newDatagram(256);

		    udc.receive(dg);
		    udc.close();
		    
		    byte[] buf = dg.getData();

		    int endofline = 0;
		    count =	buf.length;
		    for (int i = 0; i < count; i++) {
			if (buf[i] == '\n') {
			    endofline = i;
			    break;
			}
		    }
		    newurl = new String(buf, 0, endofline);
		    

		    /* Unregister the datagram connection. */
		    PushRegistry.unregisterConnection("datagram://:40080");
		    
		} catch (SecurityException e) {
		    e.printStackTrace();
		} catch (IOException e) {
		    e.printStackTrace();
		}
	    } else {
		// NYI - unknown connection type
	    }
	    urlList.append(newurl, null);
	    urls.addElement(newurl);
	} else {
	    connections = PushRegistry.listConnections(false);
	    
	    /*
	     * If the MIDlet was started manually, set an alarm
	     * to restart automatically int one minute.
	     */
	    try {
		Date alarm = new Date();
		PushRegistry.registerAlarm("example.http.HttpView", 
					   alarm.getTime() + 60000);
	    } catch (ClassNotFoundException e) {
		e.printStackTrace();
	    } catch (ConnectionNotFoundException e) {
		e.printStackTrace();
	    }
	}	
	if (urlList.size() > 0) {
            display.setCurrent(urlList);
        } else {
            alert.setString("No url's configured.");
            display.setCurrent(alert, urlList);
        }
    }

    /**
     * Pause signals the thread to stop by clearing the thread field.
     * If stopped before done with the iterations it will
     * be restarted from scratch later.
     */
    public void pauseApp() {
    }

    /**
     * Destroy must cleanup everything.  The thread is signaled
     * to stop and no result is produced.
     * @param unconditional true if a forced shutdown was requested
     */
    public void destroyApp(boolean unconditional) {
        thread = null;
    }

    /**
     * Check the attributes in the descriptor that identify
     * url's and titles and initialize the lists of urls
     * and urlList.
     * <P>
     * The attributes are named "ViewTitle-n" and "ViewURL-n".
     * The value "n" must start at "1" and increment by 1.
     */
    void setupList() {
        urls = new Vector();
        urlList = new List("URLs", List.IMPLICIT);
        urlList.addCommand(headCommand);
        urlList.addCommand(getCommand);
        urlList.addCommand(postCommand);
        urlList.addCommand(exitCommand);
        urlList.addCommand(newURLCommand);
        urlList.addCommand(removeURLCommand);
        urlList.addCommand(helpCommand);
        urlList.setCommandListener(this);

        for (int n = 1; n < 100; n++) {
            String nthURL = "ViewURL-"+ n;
            String url = getAppProperty(nthURL);
            if (url == null || url.length() == 0) {
		break;
            }

            String nthTitle = "ViewTitle-" + n;
            String title = getAppProperty(nthTitle);
            if (title == null || title.length() == 0) {
		title = url;
            }

            urls.addElement(url);
            urlList.append(title, null);
        }
        urls.addElement("http://jse.east/Telco/HttpTest.txt");
        // urls.addElement(
        //         "http://dhcp-70-219:8080/examples/servlet/httpdbexport");
        // urls.addElement(
        //       "http://jse.east.sun.com/~kfinn/proxy.jar");
        // urls.addElement(
        //       "http://dhcp-70-219:8080/examples/servlet/HelloWorldKerry");
        urlList.append("Test URL", null);
    }

    /**
     * Respond to commands, including exit
     * @param c user interface command requested
     * @param s screen object initiating the request
     */
    public void commandAction(Command c, Displayable s) {
        try {
            if (c == exitCommand) {
                destroyApp(false);
                notifyDestroyed();
            } else if (c == headCommand ||
		       c == getCommand ||
		       c == postCommand ||
		       c == List.SELECT_COMMAND) {
                if (c == List.SELECT_COMMAND)
		    c = getCommand;
                requestCommand = c;

                // Display the progress screen and
                // start the thread to read the url
                int i = urlList.getSelectedIndex();

                url = (String)urls.elementAt(i);
                genProgressForm("Progress", url);
                display.setCurrent(progressForm);
                thread = new Thread(this);
                thread.start();
            } else if (c == headersCommand) {
                display.setCurrent(headerForm);
            } else if (c == requestsCommand) {
                display.setCurrent(requestForm);
            } else if (c == errorsCommand) {
                display.setCurrent(errorsForm);
            } else if (c == backCommand) {
                if (s == headerForm || s == requestForm || s == errorsForm) {
                    display.setCurrent(content);
                } else {
                    // Display the list of urls.
                    display.setCurrent(urlList);
                }
            } else if (c == cancelCommand) {
                // Signal thread to stop and put an alert.
                thread = null;
                alert.setString("Loading cancelled.");
                display.setCurrent(alert, urlList);
            } else if (c == aboutCommand) {
                About.showAbout(display);
            } else if (c == newURLCommand) {
                display.setCurrent(urlbox);
            } else if (c == removeURLCommand) {
                int i = urlList.getSelectedIndex();
                urlList.delete(i);
                urls.removeElementAt(i);
            } else if (c == okCommand && s == urlbox) {
                String newurl = urlbox.getString();
                urlList.append(newurl, null);
                urls.addElement(newurl);
                display.setCurrent(urlList);
            } else if (c == helpCommand) {
                String helpString = 
                    "Use Head, Get or Post to download a URL.\n\n" +
                    "Use 'New URL' to enter a new URL.";
                Alert alert = new Alert(null, helpString, null, null);
                alert.setTimeout(Alert.FOREVER);
                display.setCurrent(alert); 
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    /**
     * Fetch the specified url in a separate thread and update the
     * progress bar as it goes.
     * If the user cancels the fetch, the thread be changed from this thread.
     * If this happens no further updates should be made to the
     * displayable forms. Those shared objects may be re-used
     * by the next fetch.
     */
    public void run() {
        long start = 0, end = 0;
        int bytecode_count_start = 0, bytecode_count_end = 0;

        Thread mythread = Thread.currentThread();
	String method = HttpConnection.GET;
	if (requestCommand == headCommand) {
	    method = HttpConnection.HEAD;
	} else if (requestCommand == postCommand) {
	    method = HttpConnection.POST;
	}

        if (content == null) {
            content = new TextBox("Content", "", 4096, 0);
            content.addCommand(backCommand);
            content.addCommand(headersCommand);
            content.setCommandListener(this);
        }

        // Clear the buffers and forms so then can be displayed
        // even if an exception terminates reading early.
	content.setTitle("Body len = 0");
	content.setString("");
	genErrorsForm("Errors", null);
	clearForm(requestForm);
	clearForm(headerForm);
	progressGauge.setValue(1);

	HttpConnection conn = null;
	InputStream input = null;
        OutputStream output = null;
        StringBuffer b;
        String string = null;

	try {
	    long len = 0;

	    conn = (HttpConnection)Connector.open(url);
	    conn.setRequestMethod(method);
            setConfig(conn);
	    if (mythread != thread) {
		return;
	    }
	    progressGauge.setValue(2);

            for (int hops = 0; hops < 2; hops++) {
                // Send data to the server (if necessary). Then, see if
                // we're redirected. If so, hop to the new URL
                // specified by the server.
                //
                // You can choose how many hops to make by changing the
                // exit condition of this loop.
                //
                // To see an example of this, try the link
                // "http://www.sun.com/products" link, which will 
                // redirect you to a link with a session ID.

                if (method == HttpConnection.POST) {
                    output = conn.openOutputStream();
                    if (mythread != thread) {
                        return;
                    }
                    output.write("hello midlet world".getBytes());
                    output.close();
                    output = null;
                }
                HttpConnection newConn = handleRedirects(conn);
                if (conn != newConn) {
                    conn = newConn;
                } else {
                    break;
                }
            }
	    genRequestForm(conn);
            

	    input = conn.openInputStream();
	    if (mythread != thread) {
		return;
	    }
	    content.setTitle(conn.getResponseMessage() +
			     " (" + conn.getResponseCode() + ")");
	    genHeaderForm(conn);
	    progressGauge.setValue(5);
	    if (mythread != thread) {
		return;
	    }

            // Download the content of the URL. We limit our download
            // to 4096 bytes (content.getMaxSize()), as most small
            // devices may not be able to handler larger size.
            //
            // A "real program", of course, needs to handle large
            // downloads intelligently. If possible, it should work
            // with the server to limit downloads to small sizes. If
            // this is not possible, it should download only part of
            // the data and allow the user to specify which part to
            // download.

            len = conn.getLength();
            b = new StringBuffer(len >= 0 ? (int)len : 1000);
            int max = content.getMaxSize();
            if (len != -1) {
		// Read content-Length bytes, or until max is reached.
                int ch = 0;
		for (int i = 0; i < len; i++) {
		    if ((ch = input.read()) != -1) {
			if (ch <= ' ') {
                            ch = ' ';
                        }
                        b.append((char) ch);
                        if (b.length() >= max) {
                            break;
                        }
		    }
		}
	    } else {
		// Read til the connection is closed, or til max is reached.
		// (Typical HTTP/1.0 script generated output)
                int ch = 0;
                len = 0;
		while ((ch = input.read()) != -1) {
		    if (ch <= ' ') {
                        ch = ' ';
                    }
		    b.append((char)ch);
                    if (b.length() >= max) {
                        break;
                    }
		}
	    }

            string = b.toString();
                        
            if (mythread != thread) {
		return;
	    }

	    progressGauge.setValue(8);

            content.setTitle("Body len = " + b.length());
            
            if (b.length() > 0) {
                content.setString(string);
            } else {
                content.setString("no data");
            }

	    display.setCurrent(content);
	    progressGauge.setValue(9);
	} catch (OutOfMemoryError mem) {
            // Mmm, we still run out of memory, even after setting
            // max download to 4096 bytes. Tell user about the error.
            //
            // A "real program" should decide on the max download
            // size depending on available heap space, or perhaps
            // allow the user to set the max size

            b = null;
            content = null; // free memory to print error
	    // DEBUG:            System.out.println("Out of Memory");
            mem.printStackTrace();

            if (mythread != thread) {
                genErrorsForm("Memory", mem);
                display.setCurrent(errorsForm);
            }
        } catch (Exception ex) {
            ex.printStackTrace();

	    genErrorsForm("Errors", ex);
	    display.setCurrent(errorsForm);
	} finally {
            cleanUp(conn, input, output);

	    if (mythread == thread) {
                progressGauge.setValue(10);
            }
	}
    }

    /**
     * Clean up all objects used by the HttpConnection. We must
     * close the InputStream, OutputStream objects, as well as the
     * HttpConnection object, to reclaim system resources. Otherwise,
     * we may not be able to make new connections on some platforms.
     * 
     * @param conn the HttpConnection
     * @param input the InputStream of the HttpConnection, may be null
     *              if it's not yet opened.
     * @param output the OutputStream the HttpConnection, may be null
     *              if it's not yet opened.
     */
    void cleanUp(HttpConnection conn, InputStream input,
                 OutputStream output)
    {
        Thread mythread = Thread.currentThread();

        try {
            if (input != null) {
                input.close();
            }
        } catch (IOException e) {
            if (mythread == thread) {
  		genErrorsForm("InputStream close error", e);
 	    }
        }
        try {
            if (output != null) {
                output.close();
            }
        } catch (IOException e) {
            if (mythread == thread) {
  		genErrorsForm("OutStream close error", e);
 	    }
        }
        try {
            if (conn != null) {
                conn.close();
            }
        } catch (IOException e) {
            if (mythread == thread) {
 		genErrorsForm("HttpConnection close error", e);
 	    }
        }
    }

    /**
     * Check for redirect response codes and handle
     * the redirect by getting the new location and
     * opening a new connection to it.  The original
     * connection is closed.
     * The process repeats until there are no more redirects.
     * @param c the initial HttpConnection
     * @return the final HttpConnection
     */
    HttpConnection handleRedirects(HttpConnection c) throws IOException {
        while (true) {
            int code = c.getResponseCode();
            switch (code) {
            case HttpConnection.HTTP_TEMP_REDIRECT:
            case HttpConnection.HTTP_MOVED_TEMP:
            case HttpConnection.HTTP_MOVED_PERM:
                String loc = c.getHeaderField("location");
                c.close();
		// DEBUG: System.out.println("Redirecting to " + loc);
                showAlert("Redirecting to " + loc, null);
                progressGauge.setLabel(loc);

                c = (HttpConnection)Connector.open(loc);
                // TBD: Copy request properties
                continue;
            default:
                return c;
            }
        }
    }

    /**
     * Add request properties for the configuration, profiles,
     * and locale of this system.
     * @param c current HttpConnection to receive user agent header
     */
    void setConfig(HttpConnection c) throws IOException {
        String conf = System.getProperty("microedition.configuration");
        String prof = System.getProperty("microedition.profiles");
	int space = prof.indexOf(' ');
	if (space != -1) {
	    prof = prof.substring(0, space - 1);
	}

        String platform = System.getProperty("microedition.platform");
        String locale = System.getProperty("microedition.locale");
        String ua = "Profile/" + prof +
                " Configuration/" + conf +
	    " Platform/" + platform;

	// DEBUG:        System.out.println("User-Agent: " + ua);
        c.setRequestProperty("User-Agent", ua);
        
        if (locale != null) {
	    // DEBUG:       System.out.println("Content-Language: " + locale);
            c.setRequestProperty("Content-Language", locale);
        }
    }

    /**
     * Generate and fill in the Form with the header fields.
     * @param c the open connection with the result headers.
     */
    void genHeaderForm(HttpConnection c) throws IOException {
        clearForm(headerForm);
        headerForm.append(new StringItem("response message: ",
                                         c.getResponseMessage()));
        headerForm.append(new StringItem("response code: ",
                                         c.getResponseCode() + ""));
        for (int i = 0; ; i++) {
            String key = c.getHeaderFieldKey(i);
            if (key == null)
		break;
            String value = c.getHeaderField(i);
            StringItem item = new StringItem(key + ": ", value);
            headerForm.append(item);
        }
    }

    /**
     * Generate the form with the request attributes and values.
     * @param c the open connection with the request headers.
     */
    void genRequestForm(HttpConnection c) throws IOException {
        clearForm(requestForm);

        requestForm.append(new StringItem("URL: ", 
                                          c.getURL()));
        requestForm.append(new StringItem("Method: ",
                                          c.getRequestMethod()));
        requestForm.append(new StringItem("Protocol: ",
                                          c.getProtocol()));
	requestForm.append(new StringItem("Host: ",
                                          c.getHost()));
	requestForm.append(new StringItem("File: ",
                                          c.getFile()));
	requestForm.append(new StringItem("Ref: ",
                                          c.getRef()));
	requestForm.append(new StringItem("Query: ",
                                          c.getQuery()));
	requestForm.append(new StringItem("Port: ",
                                          Integer.toString(c.getPort())));
        requestForm.append(new StringItem("User-Agent: ",
			   c.getRequestProperty("User-Agent")));
        requestForm.append(new StringItem("Content-Language: ",
			   c.getRequestProperty("Content-Language")));
    }

    /**
     * Generate the options form with URL title and progress gauge.
     * @param name the title of the URL to be loaded.
     * @param url label for the progress gauge
     */
    void genProgressForm(String name, String url) {
        progressGauge.setValue(0);
        progressGauge.setLabel(url);
        progressForm.setTitle(name);
    }

    /**
     * Set the Alert to the exception message and display it.
     * @param s the Exception title string
     * @param ex the Exception 
     */
    void genErrorsForm(String s, Throwable ex) {
        clearForm(errorsForm);
        if (s != null) {
            errorsForm.setTitle(s);
        } else {
            errorsForm.setTitle("Exception");
        }

        if (ex != null) {
            ex.printStackTrace();  // debug
            errorsForm.append(ex.getClass().getName());
            errorsForm.append("\n");
            String m = ex.getMessage();
            if (m != null) {
                errorsForm.append(m);
            }
        } else {
            errorsForm.append("None");
        }
    }

    /**
     * Set the alert string and display it.
     * @param s the error message
     * @param next the screen to be shown after the Alert.
     */
    void showAlert(String s, Screen next) {
        alert.setString(s);
        if (next == null) {
            display.setCurrent(alert);
        } else {
            display.setCurrent(alert, next);
        }
    }

    /**
     * Clear out all items in a Form.
     * @param form the Form to clear.
     */
    void clearForm(Form form) {
        int s = form.size();
        for (int i = s-1; i >= 0; i--) {
            form.delete(i);
        }
    }
}
