/*
 * Copyright (c) 2005, 2012, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package sun.net.httpserver;

import java.util.*;
import java.nio.*;
import java.net.*;
import java.io.*;
import java.nio.channels.*;
import com.sun.net.httpserver.*;
import com.sun.net.httpserver.spi.*;

/**
 */
class Request {

    final static int BUF_LEN = 2048;
    final static byte CR = 13;
    final static byte LF = 10;

    private String startLine;
    private SocketChannel chan;
    private InputStream is;
    private OutputStream os;

    Request (InputStream rawInputStream, OutputStream rawout) throws IOException {
        this.chan = chan;
        is = rawInputStream;
        os = rawout;
        do {
            startLine = readLine();
            if (startLine == null) {
                return;
            }
            /* skip blank lines */
        } while (startLine.equals (""));
    }


    char[] buf = new char [BUF_LEN];
    int pos;
    StringBuffer lineBuf;

    public InputStream inputStream () {
        return is;
    }

    public OutputStream outputStream () {
        return os;
    }

    /**
     * read a line from the stream returning as a String.
     * Not used for reading headers.
     */

    public String readLine () throws IOException {
        boolean gotCR = false, gotLF = false;
        pos = 0; lineBuf = new StringBuffer();
        while (!gotLF) {
            int c = is.read();
            if (c == -1) {
                return null;
            }
            if (gotCR) {
                if (c == LF) {
                    gotLF = true;
                } else {
                    gotCR = false;
                    consume (CR);
                    consume (c);
                }
            } else {
                if (c == CR) {
                    gotCR = true;
                } else {
                    consume (c);
                }
            }
        }
        lineBuf.append (buf, 0, pos);
        return new String (lineBuf);
    }

    private void consume (int c) {
        if (pos == BUF_LEN) {
            lineBuf.append (buf);
            pos = 0;
        }
        buf[pos++] = (char)c;
    }

    /**
     * returns the request line (first line of a request)
     */
    public String requestLine () {
        return startLine;
    }

    Headers hdrs = null;

    Headers headers () throws IOException {
        if (hdrs != null) {
            return hdrs;
        }
        hdrs = new Headers();

        char s[] = new char[10];
        int firstc = is.read();
        while (firstc != LF && firstc != CR && firstc >= 0) {
            int len = 0;
            int keyend = -1;
            int c;
            boolean inKey = firstc > ' ';
            s[len++] = (char) firstc;
    parseloop:{
                while ((c = is.read()) >= 0) {
                    switch (c) {
                      case ':':
                        if (inKey && len > 0)
                            keyend = len;
                        inKey = false;
                        break;
                      case '\t':
                        c = ' ';
                      case ' ':
                        inKey = false;
                        break;
                      case CR:
                      case LF:
                        firstc = is.read();
                        if (c == CR && firstc == LF) {
                            firstc = is.read();
                            if (firstc == CR)
                                firstc = is.read();
                        }
                        if (firstc == LF || firstc == CR || firstc > ' ')
                            break parseloop;
                        /* continuation */
                        c = ' ';
                        break;
                    }
                    if (len >= s.length) {
                        char ns[] = new char[s.length * 2];
                        System.arraycopy(s, 0, ns, 0, len);
                        s = ns;
                    }
                    s[len++] = (char) c;
                }
                firstc = -1;
            }
            while (len > 0 && s[len - 1] <= ' ')
                len--;
            String k;
            if (keyend <= 0) {
                k = null;
                keyend = 0;
            } else {
                k = String.copyValueOf(s, 0, keyend);
                if (keyend < len && s[keyend] == ':')
                    keyend++;
                while (keyend < len && s[keyend] <= ' ')
                    keyend++;
            }
            String v;
            if (keyend >= len)
                v = new String();
            else
                v = String.copyValueOf(s, keyend, len - keyend);

            if (hdrs.size() >= ServerConfig.getMaxReqHeaders()) {
                throw new IOException("Maximum number of request headers (" +
                        "sun.net.httpserver.maxReqHeaders) exceeded, " +
                        ServerConfig.getMaxReqHeaders() + ".");
            }

            hdrs.add (k,v);
        }
        return hdrs;
    }

    /**
     * Implements blocking reading semantics on top of a non-blocking channel
     */

    static class ReadStream extends InputStream {
        SocketChannel channel;
        SelectorCache sc;
        Selector selector;
        ByteBuffer chanbuf;
        SelectionKey key;
        int available;
        byte[] one;
        boolean closed = false, eof = false;
        ByteBuffer markBuf; /* reads may be satisifed from this buffer */
        boolean marked;
        boolean reset;
        int readlimit;
        static long readTimeout;
        ServerImpl server;

        static {
            readTimeout = ServerConfig.getReadTimeout();
        }

        public ReadStream (ServerImpl server, SocketChannel chan) throws IOException {
            this.channel = chan;
            this.server = server;
            sc = SelectorCache.getSelectorCache();
            selector = sc.getSelector();
            chanbuf = ByteBuffer.allocate (8* 1024);
            key = chan.register (selector, SelectionKey.OP_READ);
            available = 0;
            one = new byte[1];
            closed = marked = reset = false;
        }

        public synchronized int read (byte[] b) throws IOException {
            return read (b, 0, b.length);
        }

        public synchronized int read () throws IOException {
            int result = read (one, 0, 1);
            if (result == 1) {
                return one[0] & 0xFF;
            } else {
                return -1;
            }
        }

        public synchronized int read (byte[] b, int off, int srclen) throws IOException {

            int canreturn, willreturn;

            if (closed)
                throw new IOException ("Stream closed");

            if (eof) {
                return -1;
            }

            if (reset) { /* satisfy from markBuf */
                canreturn = markBuf.remaining ();
                willreturn = canreturn>srclen ? srclen : canreturn;
                markBuf.get(b, off, willreturn);
                if (canreturn == willreturn) {
                    reset = false;
                }
            } else { /* satisfy from channel */
                canreturn = available();
                while (canreturn == 0 && !eof) {
                    block ();
                    canreturn = available();
                }
                if (eof) {
                    return -1;
                }
                willreturn = canreturn>srclen ? srclen : canreturn;
                chanbuf.get(b, off, willreturn);
                available -= willreturn;

                if (marked) { /* copy into markBuf */
                    try {
                        markBuf.put (b, off, willreturn);
                    } catch (BufferOverflowException e) {
                        marked = false;
                    }
                }
            }
            return willreturn;
        }

        public synchronized int available () throws IOException {
            if (closed)
                throw new IOException ("Stream is closed");

            if (eof)
                return -1;

            if (reset)
                return markBuf.remaining();

            if (available > 0)
                return available;

            chanbuf.clear ();
            available = channel.read (chanbuf);
            if (available > 0) {
                chanbuf.flip();
            } else if (available == -1) {
                eof = true;
                available = 0;
            }
            return available;
        }

        /**
         * block() only called when available==0 and buf is empty
         */
        private synchronized void block () throws IOException {
            long currtime = server.getTime();
            long maxtime = currtime + readTimeout;

            while (currtime < maxtime) {
                if (selector.select (readTimeout) == 1) {
                    selector.selectedKeys().clear();
                    available ();
                    return;
                }
                currtime = server.getTime();
            }
            throw new SocketTimeoutException ("no data received");
        }

        public void close () throws IOException {
            if (closed) {
                return;
            }
            channel.close ();
            selector.selectNow();
            sc.freeSelector(selector);
            closed = true;
        }

        public synchronized void mark (int readlimit) {
            if (closed)
                return;
            this.readlimit = readlimit;
            markBuf = ByteBuffer.allocate (readlimit);
            marked = true;
            reset = false;
        }

        public synchronized void reset () throws IOException {
            if (closed )
                return;
            if (!marked)
                throw new IOException ("Stream not marked");
            marked = false;
            reset = true;
            markBuf.flip ();
        }
    }

    static class WriteStream extends java.io.OutputStream {
        SocketChannel channel;
        ByteBuffer buf;
        SelectionKey key;
        SelectorCache sc;
        Selector selector;
        boolean closed;
        byte[] one;
        ServerImpl server;
        static long writeTimeout;

        static {
            writeTimeout = ServerConfig.getWriteTimeout();
        }

        public WriteStream (ServerImpl server, SocketChannel channel) throws IOException {
            this.channel = channel;
            this.server = server;
            sc = SelectorCache.getSelectorCache();
            selector = sc.getSelector();
            key = channel.register (selector, SelectionKey.OP_WRITE);
            closed = false;
            one = new byte [1];
            buf = ByteBuffer.allocate (4096);
        }

        public synchronized void write (int b) throws IOException {
            one[0] = (byte)b;
            write (one, 0, 1);
        }

        public synchronized void write (byte[] b) throws IOException {
            write (b, 0, b.length);
        }

        public synchronized void write (byte[] b, int off, int len) throws IOException {
            int l = len;
            if (closed)
                throw new IOException ("stream is closed");

            int cap = buf.capacity();
            if (cap < len) {
                int diff = len - cap;
                buf = ByteBuffer.allocate (2*(cap+diff));
            }
            buf.clear();
            buf.put (b, off, len);
            buf.flip ();
            int n;
            while ((n = channel.write (buf)) < l) {
                l -= n;
                if (l == 0)
                    return;
                block();
            }
        }

        void block () throws IOException {
            long currtime = server.getTime();
            long maxtime = currtime + writeTimeout;

            while (currtime < maxtime) {
                if (selector.select (writeTimeout) == 1) {
                    selector.selectedKeys().clear ();
                    return;
                }
                currtime = server.getTime();
            }
            throw new SocketTimeoutException ("write blocked too long");
        }


        public void close () throws IOException {
            if (closed)
                return;
            channel.close ();
            selector.selectNow();
            sc.freeSelector(selector);
            closed = true;
        }
    }
}
