// ** This class was generated with DemFGen (vers:11/09/2009)

package edu.neu.ccs.demeterf.http.classes;

import edu.neu.ccs.demeterf.lib.*;
import java.net.Socket;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.io.IOException;
import java.io.BufferedReader;
import java.io.PrintWriter;
import java.io.InputStreamReader;
import java.io.InputStream;




/** Representation of HTTPReq */
public class HTTPReq{
    protected final HTTPHead head;
    protected final List<MsgHead> keys;
    protected final ident body;

    /** Construct a(n) HTTPReq Instance */
    public HTTPReq(HTTPHead head, List<MsgHead> keys, ident body){
        this.head = head;
        this.keys = keys;
        this.body = body;
    }
    /** Is the given object Equal to this HTTPReq? */
    public boolean equals(Object o){
        if(!(o instanceof HTTPReq))return false;
        if(o == this)return true;
        HTTPReq oo = (HTTPReq)o;
        return (((Object)head).equals(oo.head))&&(((Object)keys).equals(oo.keys))&&(((Object)body).equals(oo.body));
    }

    /** Field Class for HTTPReq.head */
    public static class head extends edu.neu.ccs.demeterf.Fields.any{}
    /** Field Class for HTTPReq.keys */
    public static class keys extends edu.neu.ccs.demeterf.Fields.any{}
    /** Field Class for HTTPReq.body */
    public static class body extends edu.neu.ccs.demeterf.Fields.any{}

    /** Default Socket connection timeout (20 seconds) */
    public static final int DEFAULT_CONN_TIMEOUT = 20*1000;
    /** Default Socket Response timeout (1 second) */
    public static final int DEFAULT_RESP_TIMEOUT = 500;

    /** Create an HTTP Request with a given Header, MessageHeaders, and Body */
    public static HTTPReq create(HTTPHead req, List<MsgHead> hds, String body){
        return createNoLen(req, hds.append(new MsgHead("Content-Length",""+body.length())), body);
    }
    /** Create an HTTP Request with a given Header, MessageHeaders, and Body */
    private static HTTPReq createNoLen(HTTPHead req, List<MsgHead> hds, String body){
        return new HTTPReq(req, hds, new ident(body));
    }

    /** Create an HTTP Get Request for the given (relative) URL. Then use
     *    {@link #send(String,int) Send} for a Request/Response pair. */
    public static HTTPReq Get(String url) throws ParseException {
        return create(HTTPHead.Get(URL.parse(url)), List.<MsgHead>create(), "");
    }
    /** Create an HTTP Get Request for the given relative URL, to be sent to the
     *    given Host.  Use this method for raw Socket/Connection sends, but use
     *    {@link #send(String,int) Send} for a Request/Response pair. */
    public static HTTPReq Get(String url, String host) throws ParseException {
        return create(HTTPHead.Get(URL.parse(url)), hostHeader(host), "");
    }
    /** Create an HTTP Head Request for the given (relative) URL. Then use
     *    {@link #send(String,int) Send} for a Request/Response pair. */
    public static HTTPReq Head(String url) throws ParseException {
        return create(HTTPHead.Get(URL.parse(url)), List.<MsgHead>create(), "");
    }
    /** Create an HTTP Head Request for the given relative URL, to be sent to the
     *    given Host.  Use this method for raw Socket/Connection sends, but use
     *    {@link #send(String,int) Send} for a Request/Response pair. */
    public static HTTPReq Head(String url, String host) throws ParseException {
        return create(HTTPHead.Head(URL.parse(url)), hostHeader(host), "");
    }
    /** Create an HTTP Post Request to the given (relative) URL. Then use
     *    {@link #send(String,int) Send} for a Request/Response pair. */
    public static HTTPReq Post(String url, String body) throws ParseException {
        return create(HTTPHead.Post(URL.parse(url)), List.<MsgHead>create(), body);
    }
    /** Create an HTTP Post Request to the given relative URL, to be sent to the
     *    given Host.  Use this method for raw Socket/Connection sends, but use
     *    {@link #send(String,int) Send} for a Request Response Pair. */
    public static HTTPReq Post(String url, String host, String body) throws ParseException {
        return create(HTTPHead.Post(URL.parse(url)), hostHeader(host), body);
    }

    private static List<MsgHead> hostHeader(String h)
    { return List.<MsgHead>create(new MsgHead(new ident("Host"), new ident(h))); }
    
    /** Add the given Message Headers to this Request */
    private HTTPReq addHeaders(List<MsgHead> hs){
          return createNoLen(head,hs.append(keys),""+body);
    }
    /** Add a given Message Headers to this Request */
    public HTTPReq addHeader(String key, String val){
          return createNoLen(head,keys.append(new MsgHead(key,val)),""+body);
    }
    
    /** Request Types */
    public static enum ReqType{ GET, HEAD, POST, PUT, DELETE, TRACE, CONNECT, OPTIONS, OTHER };
    /** Return this Request's type */
    public ReqType getType(){ return head.getType(); }
    
    /** Return the URL arguments (key/value) for this request.  The URL arguments
     *    follow the base URL after a '?'.
     *    E.g.: <i>/foo/bar?bazzle=13&wizwoz='hello'</i>
     */
    public Map<String,String> urlArgs(){ return head.url.urlArgs(); }
    /** Return the Body arguments (key/value) from this request.  The Body arguments
     *    are on a single line of the body, like URLArgs, but without the '?'
     */
    public Map<String,String> bodyArgs(){ return splitArgs(""+body); }
    /** Return the relative URL, without any URL arguments */
    public String trimmedUrl(){ return head.url.trimArgs(); }
    
    /** Send this Request to the given Server/Port, and return its Response */
    public HTTPResp send(String server, int port) throws IOException {
        return send(server,port,0);
    }
    /** Send this Request to the given Server/Port, and return its Response with
     *    the given response timeout.  If the server does not respond in the given
     *    time a Socket exception will eb thrown */
    public HTTPResp send(String server, int port, int respTimeout) throws IOException {
        return send(server,port,HTTPReq.DEFAULT_CONN_TIMEOUT, respTimeout, 0);
    }
    /** Send this Request to the given Server/Port, and return its Response with
     *    the given response timeout, and limit the max response size to maxResp */
    public HTTPResp send(String server, int port, int respTimeout, long maxResp) throws IOException {
        return send(server,port,HTTPReq.DEFAULT_CONN_TIMEOUT, respTimeout, maxResp);
    }
    /** Send this Request to the given Server/Port, and return its
     *    Response, wait for the specified timout (in Milliseconds), and only
     *    accept up to "maxResp" bytes */
    public HTTPResp send(String server, int port, int connTimeout, int respTimeout, long maxResp) throws IOException {
        Socket sock = new Socket();
        // Connect to the Server... timeout if not available
        sock.connect(new InetSocketAddress(server, port), connTimeout);
        // Add the Host header and send it to a socket
        addHeaders(hostHeader(server+":"+port)).toSocket(sock);
        // Read the response from the Socket, but timeout if needed
        HTTPResp res = HTTPResp.fromSocket(sock, respTimeout, maxResp);
        sock.close();
        return res;
    }
    /** Write this Request to the given Socket */
    public void toSocket(Socket s){
        try{
             PrintWriter outt = new PrintWriter(s.getOutputStream());
             outt.print(this.toString());
             outt.flush();
             s.shutdownOutput();
        }catch(IOException e){ throw new RuntimeException(e); }
    }
    /** Read a request from a Socket, without a timeout (wait forever) */
    public static HTTPReq fromSocket(Socket s){
        return fromSocket(s,0,0);
    }
    /** Read a request from a Socket, wait forever, but limit the transmission */
    public static HTTPReq fromSocket(Socket s, long maxBytes){
        return fromSocket(s,0,maxBytes);
    }
    /** Read a request from a Socket, timeout if needed */
    public static HTTPReq fromSocket(Socket s, int respTimeout, long maxBytes){
        try{
            // Set a timeout on the response
            s.setSoTimeout(DEFAULT_RESP_TIMEOUT);
            HTTPReq req = fromInputStream(s.getInputStream(),respTimeout,maxBytes);
            s.shutdownInput();
            return req;
        }catch(IOException e){ throw new RuntimeException(e); }
    }
    /** Read a Request from an InputStream with the given Timeout */
    private static HTTPReq fromInputStream(InputStream inpt, long timeout, long maxBytes){
        try{
             BufferedReader inn = new BufferedReader(new InputStreamReader(inpt));
             String first = readLine(inn,timeout);
             if(first == null)throw new RuntimeException("Empty HTTP Header");
             HTTPHead h = HTTPHead.parse(first);
             HTTPReq.CountHds headers = HTTPReq.parseMsgHeads(inn,timeout,maxBytes,maxBytes-(first.length()+1));
             return new HTTPReq(h, headers.heads, HTTPReq.parseBody(inn,timeout,maxBytes,
                                                            maxBytes-(first.length()+1+headers.size)));
        }catch(RuntimeException e){ throw e;
        }catch(Exception e){ throw new RuntimeException(e); }
    }
    /** Keep track of the bytes read for the headers */
    static class CountHds{
        public long size;
        public List<MsgHead> heads;
        public CountHds(long s, List<MsgHead> hds){ size = s; heads = hds; }
        public CountHds add(long s, MsgHead hd){ return new CountHds(s+size, heads.push(hd)); }
    }
    /** Parse a List of Message Headers */
    static CountHds parseMsgHeads(BufferedReader in, long timeout, long maxBytes, long bytesLeft) throws Exception{
        checkMaxBytes(maxBytes, bytesLeft);
        String line;
        if((line = readLine(in,timeout)) == null || line.length() == 0)
            return new CountHds(0, List.<MsgHead>create());
        int colon = line.indexOf(":");
        return parseMsgHeads(in,timeout,maxBytes,bytesLeft-(line.length()+1))
            .add(line.length()+1, new MsgHead(new ident(line.substring(0,colon)),
                             new ident(line.substring(colon+2))));
    }
    /** Read a single Line */
    static String readLine(BufferedReader inn, long timeout) throws IOException{
        long start = System.currentTimeMillis();
        String line = "";
        while(true){
            line = null;
            try{
                line = inn.readLine();
                return line;
            }catch(java.net.SocketTimeoutException e){
                if(timeout > 0 && (System.currentTimeMillis()-start) > timeout)
                    throw e;
            }
        }
    }
    static void checkMaxBytes(long maxBytes, long bytesLeft){
        if(maxBytes > 0 && bytesLeft < 0)
            throw new RuntimeException("HTTP Max Size Reached");
    }
    /** Parse the Body of a Request */
    static ident parseBody(BufferedReader inn, long timeout, long maxBytes, long bytesLeft) throws Exception{
        checkMaxBytes(maxBytes, bytesLeft);
        long start = System.currentTimeMillis();
        StringBuffer sb = new StringBuffer();
        char[] buff = new char[1024];
        int many;
        do{
            many = 0;
            try{
                many = inn.read(buff);
                if(many>0){
                    sb.append(buff,0,many);
                    bytesLeft -= many;
                    checkMaxBytes(maxBytes, bytesLeft);
                }
            }catch(java.net.SocketTimeoutException e){
                if(timeout > 0 && (System.currentTimeMillis()-start) > timeout){
                    System.err.println("TIMEOUT: "+timeout+" TIME: "+(System.currentTimeMillis()-start));
                    throw new RuntimeException(e);
                }
            }
        }while(inn.ready() || many > 0);
        return new ident(sb.toString());
    }
    
    /** Return the Body of this Request */
    public String getBodyString(){ return body.toString(); }
    /** Return the Headers of this Request as a Map */
    public Map<String,String> getHeaders(){ return getHeaders(keys); }

    /** Return the Headers of this Request as a Map */
    static Map<String,String> getHeaders(List<MsgHead> hds){
        return hds.fold(new List.Fold<MsgHead, Map<String,String>>(){
            public Map<String,String> fold(MsgHead h, Map<String,String> m){
                return m.put(""+h.key,""+h.value);
            }
        }, Map.<String,String>create());
    }
    /** Split an argument String into key/value Map */
    static Map<String,String> splitArgs(String s){
        return List.create(s.split("&")).fold(new List.Fold<String,Map<String,String>>(){
            public Map<String,String> fold(String p, Map<String,String> m){
                String[] kv = p.split("=");
                if(kv.length < 2)return m;
                return m.put(decodeURL(kv[0]),decodeURL(kv[1]));
            }
        }, Map.<String,String>create());
    }
    
    /** Decode an encoded URL */
    public static String decodeURL(String url){
        try{ return java.net.URLDecoder.decode(url, "UTF-8");
        }catch(Exception e){ throw new RuntimeException(e); }
    }
    /** Encode a (possibly special) URL */
    public static String encodeURL(String url){
        try{ return java.net.URLEncoder.encode(url, "UTF-8");
        }catch(Exception e){ throw new RuntimeException(e); }
    }

    /** DGP method from Class PrintHeapToString */
    public String toString(){ return edu.neu.ccs.demeterf.http.classes.PrintHeapToString.PrintHeapToStringM(this); }
    /** Getter for field HTTPReq.body */
    public ident getBody(){ return body; }
    /** Getter for field HTTPReq.keys */
    public List<MsgHead> getKeys(){ return keys; }
    /** Getter for field HTTPReq.head */
    public HTTPHead getHead(){ return head; }

}