/*
 * Copyright The WildFly Authors
 * SPDX-License-Identifier: Apache-2.0
 */

package org.jboss.as.process.protocol;

import org.jboss.as.process.logging.ProcessLogger;

import javax.xml.stream.XMLStreamWriter;
import java.io.Closeable;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * @author <a href="mailto:david.lloyd@redhat.com">David M. Lloyd</a>
 */
public final class StreamUtils {

    private StreamUtils() {
    }

    public static boolean readBoolean(final InputStream input) throws IOException {
        return readUnsignedByte(input) != 0;
    }

    public static int readUnsignedShort(final InputStream input) throws IOException {
        int ch1 = input.read();
        int ch2 = input.read();
        if ((ch1 | ch2) < 0)
            throw new EOFException();
        return ((ch1 << 8) + (ch2));
    }

    public static int readInt(final InputStream in) throws IOException {
        int ch1 = in.read();
        int ch2 = in.read();
        int ch3 = in.read();
        int ch4 = in.read();
        if ((ch1 | ch2 | ch3 | ch4) < 0)
            throw new EOFException();
        return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + (ch4 << 0));
    }

    public static void readFully(final InputStream in, byte[] b) throws IOException {
        readFully(in, b, 0, b.length);
    }

    public static void readFully(final InputStream in, byte[] b, int off, int len) throws IOException {
        if (len < 0)
            throw new IndexOutOfBoundsException();
        int n = 0;
        while (n < len) {
            int count = in.read(b, off + n, len - n);
            if (count < 0)
                throw ProcessLogger.ROOT_LOGGER.readBytes(n);
            n += count;
        }
    }

    public static long readLong(final InputStream in) throws IOException {
        byte[] bytes = new byte[8];
        readFully(in, bytes, 0, 8);
        return (((long)bytes[0] << 56) +
                ((long)(bytes[1] & 255) << 48) +
                ((long)(bytes[2] & 255) << 40) +
                ((long)(bytes[3] & 255) << 32) +
                ((long)(bytes[4] & 255) << 24) +
                ((bytes[5] & 255) << 16) +
                ((bytes[6] & 255) <<  8) +
                ((bytes[7] & 255) <<  0));
    }

    public static void writeString(final OutputStream output, final Object o) throws IOException {
        writeString(output, o.toString());
    }

    public static void writeString(final OutputStream output, final String s) throws IOException {
        final int length = s.length();

        int strIdx = 0;
        while (strIdx < length) {
            writeChar(output, s.charAt(strIdx ++));
        }
    }

    public static void writeChar(final OutputStream output, final char c) throws IOException {

        if (c >= 0x20 && c <= 0x7f) {
            output.write((byte)c);
        } else if (c <= 0x07ff) {
            output.write((byte)(0xc0 | 0x1f & c >> 6));
            output.write((byte)(0x80 | 0x3f & c));
        } else {
            output.write((byte)(0xe0 | 0x0f & c >> 12));
            output.write((byte)(0x80 | 0x3f & c >> 6));
            output.write((byte)(0x80 | 0x3f & c));
        }
    }


    public static void writeShort(final OutputStream out, final int value) throws IOException {
        out.write(value >>> 8);
        out.write(value);
    }

    public static void writeInt(final OutputStream out, final int v) throws IOException {
        out.write((v >>> 24) & 0xFF);
        out.write((v >>> 16) & 0xFF);
        out.write((v >>>  8) & 0xFF);
        out.write((v >>>  0) & 0xFF);
    }

    public static void writeLong(final OutputStream out, final long v) throws IOException {
        out.write((byte) (v >>> 56) & 0xFF);
        out.write((byte) (v >>> 48) & 0xFF);
        out.write((byte) (v >>> 40) & 0xFF);
        out.write((byte) (v >>> 32) & 0xFF);
        out.write((byte) (v >>> 24) & 0xFF);
        out.write((byte) (v >>> 16) & 0xFF);
        out.write((byte) (v >>>  8) & 0xFF);
        out.write((byte) (v >>>  0) & 0xFF);
    }

    public static void writeBoolean(final OutputStream os, final boolean b) throws IOException {
        os.write(b ? 1 : 0);
    }

    public static byte readByte(final InputStream stream) throws IOException {
        int b = stream.read();
        if (b == -1) {
            throw new EOFException();
        }
        return (byte) b;
    }

    public static int readUnsignedByte(final InputStream stream) throws IOException {
        int b = stream.read();
        if (b == -1) {
            throw new EOFException();
        }
        return b;
    }

    public static void copyStream(final InputStream in, final OutputStream out) throws IOException {
        final byte[] bytes = new byte[8192];
        int cnt;
        while ((cnt = in.read(bytes)) != -1) {
            out.write(bytes, 0, cnt);
        }
    }

    public static String readUTFZBytes(final InputStream input) throws IOException {
        final StringBuilder builder = new StringBuilder();
        for (;;) {
            final int c = readUTFChar(input);
            if (c == -1) {
                return builder.toString();
            }
            builder.append((char) c);
        }
    }

    private static int readUTFChar(final InputStream input) throws IOException {
        final int a = input.read();
        if (a < 0) {
            throw new EOFException();
        } else if (a == 0) {
            return -1;
        } else if (a < 0x80) {
            return (char)a;
        } else if (a < 0xc0) {
            throw ProcessLogger.ROOT_LOGGER.invalidByte();
        } else if (a < 0xe0) {
            final int b = input.read();
            if (b == -1) {
                throw new EOFException();
            } else if ((b & 0xc0) != 0x80) {
            throw ProcessLogger.ROOT_LOGGER.invalidByte();
            }
            return (a & 0x1f) << 6 | b & 0x3f;
        } else if (a < 0xf0) {
            final int b = input.read();
            if (b == -1) {
                throw new EOFException();
            } else if ((b & 0xc0) != 0x80) {
            throw ProcessLogger.ROOT_LOGGER.invalidByte();
            }
            final int c = input.read();
            if (c == -1) {
                throw new EOFException();
            } else if ((c & 0xc0) != 0x80) {
            throw ProcessLogger.ROOT_LOGGER.invalidByte();
            }
            return (a & 0x0f) << 12 | (b & 0x3f) << 6 | c & 0x3f;
        } else {
            throw ProcessLogger.ROOT_LOGGER.invalidByte();
        }
    }

    public static void writeUTFZBytes(final OutputStream outputStream, String string) throws IOException {
        final int len = string.length();
        for (int i = 0; i < len; i ++) {
            writeChar(outputStream, string.charAt(i));
        }
        outputStream.write(0);
    }

    public static void safeClose(final Closeable closeable) {
        if (closeable != null) try {
            closeable.close();
        } catch (Throwable t) {
            ProcessLogger.PROTOCOL_LOGGER.failedToCloseResource(t, closeable);
        }
    }

    public static void safeClose(final Socket socket) {
        if (socket != null) try {
            socket.close();
        } catch (Throwable t) {
            ProcessLogger.PROTOCOL_LOGGER.failedToCloseResource(t, socket);
        }
    }

    public static void safeClose(final ServerSocket serverSocket) {
        if (serverSocket != null) try {
            serverSocket.close();
        } catch (IOException e) {
            ProcessLogger.PROTOCOL_LOGGER.failedToCloseServerSocket(e, serverSocket);
        }
    }

    public static void safeClose(final XMLStreamWriter writer) {
        if (writer != null) try {
            writer.close();
        } catch (Throwable t) {
            ProcessLogger.PROTOCOL_LOGGER.failedToCloseResource(t, writer);
        }
    }
}
