/*
 * Copyright (c) 2002-2011 JSCAPE
 * 1147 S. 53rd Pl., Mesa, Arizona, 85206, U.S.A.
 * All rights reserved.
 *
 * This software is the confidential and proprietary information of
 * JSCAPE. ("Confidential Information").  You shall not disclose such
 * Confidential Information and shall use it only in accordance with
 *  the terms of the license agreement you entered into with JSCAPE.
 */

package com.jscape.inet.http.protocol.marshaling;

import com.jscape.util.ArrayTools;
import com.jscape.util.io.ByteArrayOutputStream;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;

import static com.jscape.util.IOTools.EOF;

/**
 * @author Alex Usun
 */
public class LineCodec {

    private static final byte[] CRLF = { '\r', '\n' };
    private static final byte[] SPACE = { ' ' };
    private static final int MAX_LINE_LENGTH = 65536;

    public static void writeBytes(byte[] data, byte[] eol, OutputStream out)
            throws IOException {
        out.write(data);
        out.write(eol);
    }

    public static void writeBytesCRLF(byte[] data, OutputStream out)
            throws IOException {
        writeBytes(data, CRLF, out);
    }

    public static void writeString(String line, Charset charset, byte[] eol, OutputStream out)
            throws IOException {
        writeBytes(line.getBytes(charset), eol, out);
    }

    public static void writeStringCRLF(String line, Charset charset, OutputStream out)
            throws IOException {
        writeString(line, charset, CRLF, out);
    }

    public static void writeCRLF(OutputStream out)
            throws IOException {
        out.write(CRLF);
    }

    public static byte[] readBytes(byte[] eol, InputStream in)
            throws IOException {
        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
        int read;
        boolean eolFound = false;
        while ((read = in.read()) != EOF) {
            buffer.write(read);

            if (ArrayTools.ends(buffer.buffer(), eol, buffer.position())) {
                eolFound = true;
                break;
            } else if (buffer.position() >= MAX_LINE_LENGTH) {
                break;
            }
        }

        if (read == EOF && buffer.position() == 0) {
            return null;
        }

        int length = eolFound ? buffer.position() - eol.length : buffer.position();

        return ArrayTools.copy(buffer.buffer(), 0, new byte[length]);
    }

    public static byte[] readBytes(byte[][] eols, InputStream in)
            throws IOException {
        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
        int read;
        byte[] foundEol = null;
        reading:
        while ((read = in.read()) != EOF) {
            buffer.write(read);

            for (byte[] eol : eols) {
                if (ArrayTools.ends(buffer.buffer(), eol, buffer.position())) {
                    foundEol = eol;
                    break reading;
                }
            }

            if (buffer.position() >= MAX_LINE_LENGTH) {
                break;
            }
        }

        if (read == EOF && buffer.position() == 0) {
            return null;
        }

        int length = foundEol != null ? buffer.position() - foundEol.length : buffer.position();

        return ArrayTools.copy(buffer.buffer(), 0, new byte[length]);
    }

    public static String readStringCRLF(Charset charset, InputStream in)
            throws IOException {
        byte[] data = readBytesCRLF(in);
        return data != null ? new String(data, charset) : null;
    }

    public static String readStringSpace(Charset charset, InputStream in)
            throws IOException {
        byte[] data = readBytesSpace(in);
        return data != null ? new String(data, charset) : null;
    }

    public static byte[] readBytesCRLF(InputStream in)
            throws IOException {
        return readBytes(CRLF, in);
    }

    public static byte[] readBytesSpace(InputStream in)
            throws IOException {
        return readBytes(SPACE, in);
    }

    private LineCodec() {
        // empty
    }
}
