/*
 * @(#)HTTPClient.java    1.0    11/14/01
 *
 * Copyright (C) 2001-2005  Danger, Inc.
 * 3101 Park Blvd, Palo Alto CA 94306
 * All rights reserved.
 */

package com.danger.HTTPClient;

import java.io.IOException;
import danger.app.Application;
import danger.app.Event;
import danger.net.HTTPConnection;
import danger.net.HTTPTransaction;
import danger.ui.*;
import danger.util.Decompressor;
import java.util.Hashtable;
import java.util.zip.DataFormatException;


public class HTTPClient
    extends Application
    implements Resources, Events
{
    //*    --------------------    HTTPClient
    public
    HTTPClient()
    {
        mWindow = (HTTPClientWindow) getResources().getScreen(ID_MAIN_SCREEN);
        mWindow.setFlags(DEFAULT_FLAGS);
        mWindow.getActionMenu().setListenerForAllItems(this);
    }

    //*    --------------------    ReceiveEvent
    public boolean
    receiveEvent(Event e)
    {
        short   flags;
        int     sequenceID;
        String  headers;

        switch (e.type) {
            case EVENT_GET_VERSION:
                HTTPConnection.getVersion();
                break;
            
            case EVENT_GO:
                flags = mWindow.getFlags();
                sequenceID = 42;
                HTTPConnection.post(mWindow.getURL(), mWindow.getHeaders(), mWindow.getPostData(), flags, sequenceID);
                mWindow.setChunk(0, 0);
                mWindow.setResponseHeaders("(pending)");
                mWindow.setBody("");
                break;
                
            case EVENT_GO_3:
                // send 3 requests in quick succession, to trigger a bug chris desalvo found
                flags = mWindow.getFlags();
                HTTPConnection.post(mWindow.getURL(), mWindow.getHeaders(), mWindow.getPostData(), flags, 42);
                HTTPConnection.post(mWindow.getURL(), mWindow.getHeaders(), mWindow.getPostData(), flags, 43);
                HTTPConnection.post(mWindow.getURL(), mWindow.getHeaders(), mWindow.getPostData(), flags, 44);
                mWindow.setChunk(0, 0);
                mWindow.setResponseHeaders("(pending)");
                mWindow.setBody("");
                break;
                
            case EVENT_TRANSCODE_DIALOG: {
                DialogWindow d = (DialogWindow) getResources().getDialog(ID_TRANSCODE_DIALOG);
                d.setListener(this);
                d.show();
            }
            break;
            
            case EVENT_TRANSCODE:
                addTranscodeHeader((DialogWindow) e.argument);
                break;
                
            case EVENT_PRESET_GOOGLE:
                mWindow.setURL("http://www.google.com");
                mWindow.setHeaders("");
                mWindow.setPostData("");
                break;
                
            case EVENT_PRESET_ONION:
                mWindow.setURL("http://mobile.theonion.com");
                mWindow.setHeaders("");
                mWindow.setPostData("");
                break;

            case EVENT_PRESET_MOVIEFONE:
                mWindow.setURL("https://moviefone.movietickets.com/pre_purchase.asp?afid=moviefone&house_id=3662&movie_id=35399");
                mWindow.setHeaders("");
                mWindow.setPostData("");
                break;
        }
        return super.receiveEvent(e);
    }

    //*    --------------------    Network
    public void
    networkEvent(Object s)
    {
        int    command;
        int sequence;
        int    response;
        String responseMessage;
        String headerBlock;
        Hashtable headers;
        byte[] data;

        if (s instanceof HTTPTransaction) {
            HTTPTransaction ht = (HTTPTransaction) s;
            
            command = ht.getCommand();
            sequence = ht.getSequenceID();
            response = ht.getResponse();
            responseMessage = ht.getResponseMessage();
            headerBlock = ht.getHTTPHeaders();
            headers = getHeaders(headerBlock);
            //System.err.println("HEADERS: " + headers);

            String encoding = (String) headers.get("content-encoding");
            //System.err.println("ENCODING: _" + encoding + "_");
            if (encoding != null)
            {
                if (encoding.equals("deflate"))
                {
                    data = inflate(ht.getBytes());
                }
                else
                {
                    data = ("(unable to decode content-encoding: " + encoding + ")").getBytes();
                }
            }
            else
            {
                data = ht.getBytes();
            }
            
            int chunkOffset = ht.getOffset();
            int chunkLength = ht.getLength();
            int totalLength = ht.getTotal();
            
            if (chunkOffset == 0) {
                mData = data;
                // response headers are only attached to the first chunk
                if (response != 200) {
                    headerBlock = response + " " + responseMessage + "\n" + headerBlock;
                }
                mWindow.setResponseHeaders(headerBlock);
            } else {
                byte[] newData = new byte[mData.length + data.length];
                System.arraycopy(mData, 0, newData, 0, mData.length);
                System.arraycopy(data, 0, newData, mData.length, data.length);
                mData = newData;
            }
            
            if ((chunkOffset != 0) || (chunkLength != totalLength)) {
                // chunked!
                mWindow.setChunk(chunkOffset + chunkLength, totalLength);
            } else {
                mWindow.setChunk(0, 0);
            }
            
            boolean isImage = false;
            if (mData.length > 10) {
                // is it an image?
                if ((mData[0] == (byte)0x89) && (mData[1] == (byte)'P') && (mData[2] == (byte)'N') && (mData[3] == (byte)'G')) {
                    // PNG
                    try {
                        mWindow.setImage(Bitmap.newFromPNGData(mData));
                    } catch (IOException x) {
                        mWindow.setBody("(undecodable PNG)");
                    }
                    isImage = true;
                }
                if ((mData[6] == (byte)'J') && (mData[7] == (byte)'F') && (mData[8] == (byte)'I') && (mData[9] == (byte)'F')) {
                    // JPG
                    try {
                        mWindow.setImage(Bitmap.newFromJPEGData(mData));
                    } catch (IOException x) {
                        mWindow.setBody("(undecodable JPG)");
                    }
                    isImage = true;
                }
            }
            
            if (! isImage) {
                try {
                    mWindow.setBody(new String(mData));
                } catch (Throwable t) {
                    mWindow.setBody("(undecodable data)");
                }
            }
        }
    }

    private Hashtable
    getHeaders(String block)
    {
        Hashtable headers = new Hashtable();
        int start = 0;
        int end = 0;
        while (start < block.length())
        {
            end = block.indexOf("\n", start);
            if (end == -1)
            {
                end = block.length();
            }
            //System.err.println("RANGE: (" + start + ", " + end + ")");
            String item = block.substring(start, end);
            int colon = item.indexOf(":");
            if (colon == -1)
            {
                // invalid header, skip for now
                end++;
                start = end;
                continue;
            }

            String name = item.substring(0, colon).toLowerCase().trim();
            String value = item.substring(colon+1).toLowerCase().trim();
            //System.err.println("NAME: " + name + " - " + value);

            Object oldValue = headers.get(name);
            String[] newValue;
            if (oldValue == null)
            {
                headers.put(name, value);
            }
            else
            {
                if (oldValue instanceof String)
                {
                    newValue = new String[2];
                    newValue[0] = (String) oldValue;
                }
                // String[]
                else
                {
                    String[] tmp = (String[]) oldValue;
                    newValue = new String[tmp.length+1];
                    System.arraycopy(tmp, 0, newValue, 0, tmp.length);
                }
                newValue[newValue.length-1] = value;
                headers.put(name, newValue);
            }

            end = end++;
            start = end;
        }
        return headers;
    }
 
    private byte[]
    inflate(byte[] input)
    {
        int bufsize = 5 * input.length;
        byte[] output = null;
        int outsize = 0;
        boolean done = false;

        while (!done)
        {
            output = new byte[bufsize];

            Decompressor dc = new Decompressor();
            dc.setInput(input);
            outsize = 0;
            try
            {
                outsize = dc.inflate(output);
            }
            catch (DataFormatException ex)
            {
                System.err.println(ex);
                output = ("(unable to decompress: " + ex + ")").getBytes();
                outsize = output.length;
                break;
            }
            dc.end();

            done = outsize < bufsize;
            // exponentially increase the buffer size until it fits
            bufsize = 5 * bufsize;
            if (bufsize > MAX_INFLATE_BUFFER_SIZE)
            {
                output = ("(oversized data: " + bufsize + " > " + MAX_INFLATE_BUFFER_SIZE + ")").getBytes();
                outsize = output.length;
                break;
            }
        }

        byte[] data = new byte[outsize];
        System.arraycopy(output, 0, data, 0, outsize);

        return data;
    }

    private void
    addTranscodeHeader (DialogWindow d)
    {
        String widthStr = ((TextField) d.getDescendantWithID(ID_TRANSCODE_WIDTH_FIELD)).toString();
        String heightStr = ((TextField) d.getDescendantWithID(ID_TRANSCODE_HEIGHT_FIELD)).toString();
        
        String header = "X-Transcode-Image: ";
        if (((CheckBox) d.getDescendantWithID(ID_TRANSCODE_PROPORTIONAL)).getValue() != 0) {
            header += "proportional; ";
        }
        if (widthStr.length() > 0) {
            header += "width=" + widthStr + "; ";
        }
        if (heightStr.length() > 0) {
            header += "height=" + heightStr + "; ";
        }
        if (header.length() == "X-Transcode-Image: ".length()) {
            // skip it
            return;
        }
        header = header.substring(0, header.length() - 2);
        mWindow.setHeaders(header + "\n" + mWindow.getHeaders());
    }
    
    //*    --------------------    Resume
    public void
    resume()
    {
        mWindow.show();
    }
    
    //*    --------------------    Suspend
    public void
    suspend()
    {
    }

    //*    --------------------------------    Class Variables
    static HTTPClientWindow    mWindow;

    private byte[] mData;
    
    static final short DEFAULT_FLAGS = HTTPConnection.RETURN_HEADERS;
    static final int MAX_INFLATE_BUFFER_SIZE = 1024*1024*1024; // 1MB
}
