/* Copyright (c) 2001-2020, The HSQL Development Group
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the HSQL Development Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


package org.hsqldb.result;

import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;

import org.hsqldb.SessionInterface;
import org.hsqldb.error.Error;
import org.hsqldb.error.ErrorCode;
import org.hsqldb.lib.DataOutputStream;
import org.hsqldb.lib.HsqlByteArrayOutputStream;
import org.hsqldb.rowio.RowOutputInterface;

/**
 * Sub-class of Result for communicating Blob and Clob operations.<p>
 *
 * @author Fred Toussi (fredt@users dot sourceforge.net)
 * @version 2.5.1
 * @since 1.9.0
 */
public final class ResultLob extends Result {

    public interface LobResultTypes {

        int REQUEST_GET_BYTES                 = 1;
        int REQUEST_SET_BYTES                 = 2;
        int REQUEST_GET_CHARS                 = 3;
        int REQUEST_SET_CHARS                 = 4;
        int REQUEST_GET_BYTE_PATTERN_POSITION = 5;
        int REQUEST_GET_CHAR_PATTERN_POSITION = 6;
        int REQUEST_CREATE_BYTES              = 7;
        int REQUEST_CREATE_CHARS              = 8;
        int REQUEST_TRUNCATE                  = 9;
        int REQUEST_GET_LENGTH                = 10;
        int REQUEST_GET_LOB                   = 11;
        int REQUEST_DUPLICATE_LOB             = 12;

        // non-network
        int REQUEST_GET_TRUNCATE_LENGTH = 13;

        //
        int RESPONSE_GET_BYTES                 = 21;
        int RESPONSE_SET                       = 22;
        int RESPONSE_GET_CHARS                 = 23;
        int RESPONSE_GET_BYTE_PATTERN_POSITION = 25;
        int RESPONSE_GET_CHAR_PATTERN_POSITION = 26;
        int RESPONSE_CREATE_BYTES              = 27;
        int RESPONSE_CREATE_CHARS              = 28;
        int RESPONSE_TRUNCATE                  = 29;
    }

    long        lobID;
    int         subType;
    long        blockOffset;
    long        blockLength;
    byte[]      byteBlock;
    char[]      charBlock;
    Reader      reader;
    InputStream stream;

    private ResultLob() {
        super(ResultConstants.LARGE_OBJECT_OP);
    }

    public static ResultLob newLobGetLengthRequest(long id) {

        ResultLob result = new ResultLob();

        result.subType = LobResultTypes.REQUEST_GET_LENGTH;
        result.lobID   = id;

        return result;
    }

    public static ResultLob newLobGetBytesRequest(long id, long offset,
            int length) {

        ResultLob result = new ResultLob();

        result.subType     = LobResultTypes.REQUEST_GET_BYTES;
        result.lobID       = id;
        result.blockOffset = offset;
        result.blockLength = length;

        return result;
    }

    public static ResultLob newLobGetCharsRequest(long id, long offset,
            int length) {

        ResultLob result = new ResultLob();

        result.subType     = LobResultTypes.REQUEST_GET_CHARS;
        result.lobID       = id;
        result.blockOffset = offset;
        result.blockLength = length;

        return result;
    }

    public static ResultLob newLobSetBytesRequest(long id, long offset,
            byte[] block) {

        ResultLob result = new ResultLob();

        result.subType     = LobResultTypes.REQUEST_SET_BYTES;
        result.lobID       = id;
        result.blockOffset = offset;
        result.byteBlock   = block;
        result.blockLength = block.length;

        return result;
    }

    public static ResultLob newLobSetCharsRequest(long id, long offset,
            char[] chars) {

        ResultLob result = new ResultLob();

        result.subType     = LobResultTypes.REQUEST_SET_CHARS;
        result.lobID       = id;
        result.blockOffset = offset;
        result.charBlock   = chars;
        result.blockLength = chars.length;

        return result;
    }

    public static ResultLob newLobTruncateRequest(long id, long offset) {

        ResultLob result = new ResultLob();

        result.subType     = LobResultTypes.REQUEST_TRUNCATE;
        result.lobID       = id;
        result.blockOffset = offset;

        return result;
    }

    public static ResultLob newLobGetBytesResponse(long id, long offset,
            byte[] block) {

        ResultLob result = new ResultLob();

        result.subType     = LobResultTypes.RESPONSE_GET_BYTES;
        result.lobID       = id;
        result.blockOffset = offset;
        result.byteBlock   = block;
        result.blockLength = block.length;

        return result;
    }

    public static ResultLob newLobGetCharsResponse(long id, long offset,
            char[] chars) {

        ResultLob result = new ResultLob();

        result.subType     = LobResultTypes.RESPONSE_GET_CHARS;
        result.lobID       = id;
        result.blockOffset = offset;
        result.charBlock   = chars;
        result.blockLength = chars.length;

        return result;
    }

    public static ResultLob newLobSetResponse(long id, long length) {

        ResultLob result = new ResultLob();

        result.subType     = LobResultTypes.RESPONSE_SET;
        result.lobID       = id;
        result.blockLength = length;

        return result;
    }

    public static ResultLob newLobGetBytePatternPositionRequest(long id,
            byte[] pattern, long offset) {

        ResultLob result = new ResultLob();

        result.subType     = LobResultTypes.REQUEST_GET_BYTE_PATTERN_POSITION;
        result.lobID       = id;
        result.blockOffset = offset;
        result.byteBlock   = pattern;
        result.blockLength = pattern.length;

        return result;
    }

    public static ResultLob newLobGetBytePatternPositionRequest(long id,
            long otherId, long offset) {

        ResultLob result = new ResultLob();

        result.subType     = LobResultTypes.REQUEST_GET_BYTE_PATTERN_POSITION;
        result.lobID       = id;
        result.blockOffset = offset;

        return result;
    }

    public static ResultLob newLobGetCharPatternPositionRequest(long id,
            char[] pattern, long offset) {

        ResultLob result = new ResultLob();

        result.subType     = LobResultTypes.REQUEST_GET_CHAR_PATTERN_POSITION;
        result.lobID       = id;
        result.blockOffset = offset;
        result.charBlock   = pattern;
        result.blockLength = pattern.length;

        return result;
    }

    public static ResultLob newLobGetCharPatternPositionRequest(long id,
            long otherId, long offset) {

        ResultLob result = new ResultLob();

        result.subType     = LobResultTypes.REQUEST_GET_CHAR_PATTERN_POSITION;
        result.lobID       = id;
        result.blockOffset = offset;
        result.blockLength = otherId;

        return result;
    }

    public static ResultLob newLobCreateBlobRequest(long sessionID,
            long lobID, InputStream stream, long length) {

        ResultLob result = new ResultLob();

        result.lobID       = lobID;
        result.subType     = LobResultTypes.REQUEST_CREATE_BYTES;
        result.blockLength = length;
        result.stream      = stream;

        return result;
    }

    public static ResultLob newLobCreateClobRequest(long sessionID,
            long lobID, Reader reader, long length) {

        ResultLob result = new ResultLob();

        result.lobID       = lobID;
        result.subType     = LobResultTypes.REQUEST_CREATE_CHARS;
        result.blockLength = length;
        result.reader      = reader;

        return result;
    }

    public static ResultLob newLobGetTruncateLength(long id) {

        ResultLob result = new ResultLob();

        result.subType = LobResultTypes.REQUEST_GET_TRUNCATE_LENGTH;
        result.lobID   = id;

        return result;
    }

    public static ResultLob newLobCreateBlobResponse(long id) {

        ResultLob result = new ResultLob();

        result.subType = LobResultTypes.RESPONSE_CREATE_BYTES;
        result.lobID   = id;

        return result;
    }

    public static ResultLob newLobCreateClobResponse(long id) {

        ResultLob result = new ResultLob();

        result.subType = LobResultTypes.RESPONSE_CREATE_CHARS;
        result.lobID   = id;

        return result;
    }

    public static ResultLob newLobTruncateResponse(long id, long length) {

        ResultLob result = new ResultLob();

        result.subType     = LobResultTypes.RESPONSE_TRUNCATE;
        result.lobID       = id;
        result.blockLength = length;

        return result;
    }

    public static ResultLob newLobGetRequest(long id, long offset,
            long length) {

        ResultLob result = new ResultLob();

        result.subType     = LobResultTypes.REQUEST_GET_LOB;
        result.lobID       = id;
        result.blockOffset = offset;
        result.blockLength = length;

        return result;
    }

    public static ResultLob newLobDuplicateRequest(long id) {

        ResultLob result = new ResultLob();

        result.subType = LobResultTypes.REQUEST_DUPLICATE_LOB;
        result.lobID   = id;

        return result;
    }

    public static ResultLob newLob(DataInputStream dataInput,
                                   boolean readTerminate) throws IOException {

        ResultLob result = new ResultLob();

        result.databaseID = dataInput.readInt();
        result.sessionID  = dataInput.readLong();
        result.lobID      = dataInput.readLong();
        result.subType    = dataInput.readInt();

        switch (result.subType) {

            case LobResultTypes.REQUEST_CREATE_BYTES :
            case LobResultTypes.REQUEST_CREATE_CHARS :
                result.blockOffset = dataInput.readLong();
                result.blockLength = dataInput.readLong();
                result.stream      = dataInput;
                break;

            case LobResultTypes.REQUEST_GET_LOB :
            case LobResultTypes.REQUEST_DUPLICATE_LOB :

            //
            case LobResultTypes.REQUEST_GET_BYTES :
            case LobResultTypes.REQUEST_GET_CHARS :
                result.blockOffset = dataInput.readLong();
                result.blockLength = dataInput.readLong();
                break;

            case LobResultTypes.REQUEST_SET_BYTES :
            case LobResultTypes.REQUEST_GET_BYTE_PATTERN_POSITION :
                result.blockOffset = dataInput.readLong();
                result.blockLength = dataInput.readLong();
                result.byteBlock   = new byte[(int) result.blockLength];

                dataInput.readFully(result.byteBlock);
                break;

            case LobResultTypes.REQUEST_SET_CHARS :
            case LobResultTypes.REQUEST_GET_CHAR_PATTERN_POSITION :
                result.blockOffset = dataInput.readLong();
                result.blockLength = dataInput.readLong();
                result.charBlock   = new char[(int) result.blockLength];

                for (int i = 0; i < result.charBlock.length; i++) {
                    result.charBlock[i] = dataInput.readChar();
                }
                break;

            case LobResultTypes.REQUEST_GET_LENGTH :
            case LobResultTypes.REQUEST_TRUNCATE :
                result.blockOffset = dataInput.readLong();
                break;

            case LobResultTypes.RESPONSE_GET_BYTES :
                result.blockOffset = dataInput.readLong();
                result.blockLength = dataInput.readLong();
                result.byteBlock   = new byte[(int) result.blockLength];

                dataInput.readFully(result.byteBlock);
                break;

            case LobResultTypes.RESPONSE_GET_CHARS :
                result.blockOffset = dataInput.readLong();
                result.blockLength = dataInput.readLong();
                result.charBlock   = new char[(int) result.blockLength];

                for (int i = 0; i < result.charBlock.length; i++) {
                    result.charBlock[i] = dataInput.readChar();
                }
                break;

            case LobResultTypes.RESPONSE_SET :
            case LobResultTypes.RESPONSE_CREATE_BYTES :
            case LobResultTypes.RESPONSE_CREATE_CHARS :
            case LobResultTypes.RESPONSE_TRUNCATE :
                result.blockLength = dataInput.readLong();
                break;

            case LobResultTypes.RESPONSE_GET_BYTE_PATTERN_POSITION :
            case LobResultTypes.RESPONSE_GET_CHAR_PATTERN_POSITION :
                result.blockOffset = dataInput.readLong();
                break;

            default :
                throw Error.runtimeError(ErrorCode.U_S0500, "ResultLob");
        }

        if (readTerminate) {
            dataInput.readByte();
        }

        return result;
    }

    public void write(SessionInterface session, DataOutputStream dataOut,
                      RowOutputInterface rowOut) throws IOException {

        writeBody(session, dataOut);
        dataOut.writeByte(ResultConstants.NONE);
        dataOut.flush();
    }

    public void writeBody(SessionInterface session,
                          DataOutputStream dataOut) throws IOException {

        switch (subType) {

            case LobResultTypes.REQUEST_CREATE_BYTES :
                if (blockLength >= 0) {
                    writeCreate(session, dataOut);

                    return;
                }

                writeCreateByteSegments(session, dataOut);

                return;

            case LobResultTypes.REQUEST_CREATE_CHARS : {
                if (blockLength >= 0) {
                    writeCreate(session, dataOut);

                    return;
                }

                writeCreateCharSegments(session, dataOut);

                return;
            }
        }

        dataOut.writeByte(mode);
        dataOut.writeInt(databaseID);
        dataOut.writeLong(sessionID);
        dataOut.writeLong(lobID);
        dataOut.writeInt(subType);

        switch (subType) {

            case LobResultTypes.REQUEST_SET_BYTES :
            case LobResultTypes.REQUEST_GET_BYTE_PATTERN_POSITION :
                dataOut.writeLong(blockOffset);
                dataOut.writeLong(blockLength);
                dataOut.write(byteBlock);
                break;

            case LobResultTypes.REQUEST_SET_CHARS :
            case LobResultTypes.REQUEST_GET_CHAR_PATTERN_POSITION :
                dataOut.writeLong(blockOffset);
                dataOut.writeLong(blockLength);
                dataOut.writeChars(charBlock);
                break;

            case LobResultTypes.REQUEST_GET_LOB :
            case LobResultTypes.REQUEST_DUPLICATE_LOB :

            //
            case LobResultTypes.REQUEST_GET_BYTES :
            case LobResultTypes.REQUEST_GET_CHARS :
                dataOut.writeLong(blockOffset);
                dataOut.writeLong(blockLength);
                break;

            case LobResultTypes.REQUEST_GET_LENGTH :
            case LobResultTypes.REQUEST_TRUNCATE :
                dataOut.writeLong(blockOffset);
                break;

            case LobResultTypes.RESPONSE_GET_BYTES :
                dataOut.writeLong(blockOffset);
                dataOut.writeLong(blockLength);
                dataOut.write(byteBlock);
                break;

            case LobResultTypes.RESPONSE_GET_CHARS :
                dataOut.writeLong(blockOffset);
                dataOut.writeLong(blockLength);
                dataOut.writeChars(charBlock);
                break;

            case LobResultTypes.RESPONSE_SET :
            case LobResultTypes.RESPONSE_CREATE_BYTES :
            case LobResultTypes.RESPONSE_CREATE_CHARS :
            case LobResultTypes.RESPONSE_TRUNCATE :
                dataOut.writeLong(blockLength);
                break;

            case LobResultTypes.RESPONSE_GET_BYTE_PATTERN_POSITION :
            case LobResultTypes.RESPONSE_GET_CHAR_PATTERN_POSITION :
                dataOut.writeLong(blockOffset);
                break;

            default :
                throw Error.runtimeError(ErrorCode.U_S0500, "ResultLob");
        }
    }

    private void writeCreate(SessionInterface session,
                             DataOutputStream dataOut) throws IOException {

        dataOut.writeByte(mode);
        dataOut.writeInt(databaseID);
        dataOut.writeLong(sessionID);
        dataOut.writeLong(lobID);
        dataOut.writeInt(subType);
        dataOut.writeLong(blockOffset);
        dataOut.writeLong(blockLength);

        switch (subType) {

            case LobResultTypes.REQUEST_CREATE_BYTES :
                dataOut.write(stream, blockLength);
                break;

            case LobResultTypes.REQUEST_CREATE_CHARS :
                dataOut.write(reader, blockLength);
                break;
        }
    }

    private void writeCreateByteSegments(SessionInterface session,
                                         DataOutputStream dataOut)
                                         throws IOException {

        int  bufferLength  = session.getStreamBlockSize();
        long currentOffset = blockOffset;

        dataOut.writeByte(mode);
        dataOut.writeInt(databaseID);
        dataOut.writeLong(sessionID);
        dataOut.writeLong(lobID);
        dataOut.writeInt(subType);

        HsqlByteArrayOutputStream byteArrayOS =
            new HsqlByteArrayOutputStream(bufferLength);

        byteArrayOS.reset();
        byteArrayOS.write(stream, bufferLength);

        //
        dataOut.writeLong(currentOffset);
        dataOut.writeLong(byteArrayOS.size());
        dataOut.write(byteArrayOS.getBuffer(), 0, byteArrayOS.size());

        currentOffset += byteArrayOS.size();

        if (byteArrayOS.size() < bufferLength) {
            return;
        }

        while (true) {
            byteArrayOS.reset();
            byteArrayOS.write(stream, bufferLength);

            if (byteArrayOS.size() == 0) {
                break;
            }

            dataOut.writeByte(mode);
            dataOut.writeInt(databaseID);
            dataOut.writeLong(sessionID);
            dataOut.writeLong(lobID);
            dataOut.writeInt(LobResultTypes.REQUEST_SET_BYTES);
            dataOut.writeLong(currentOffset);
            dataOut.writeLong(byteArrayOS.size());
            dataOut.write(byteArrayOS.getBuffer(), 0, byteArrayOS.size());

            currentOffset += byteArrayOS.size();

            if (byteArrayOS.size() < bufferLength) {
                break;
            }
        }
    }

    private void writeCreateCharSegments(SessionInterface session,
                                         DataOutputStream dataOut)
                                         throws IOException {

        int  bufferLength  = session.getStreamBlockSize();
        long currentOffset = blockOffset;

        dataOut.writeByte(mode);
        dataOut.writeInt(databaseID);
        dataOut.writeLong(sessionID);
        dataOut.writeLong(lobID);
        dataOut.writeInt(subType);

        HsqlByteArrayOutputStream byteArrayOS =
            new HsqlByteArrayOutputStream(bufferLength);

        byteArrayOS.reset();
        byteArrayOS.write(reader, bufferLength / 2);

        //
        dataOut.writeLong(currentOffset);
        dataOut.writeLong(byteArrayOS.size() / 2);
        dataOut.write(byteArrayOS.getBuffer(), 0, byteArrayOS.size());

        currentOffset += byteArrayOS.size() / 2;

        if (byteArrayOS.size() < bufferLength) {
            return;
        }

        while (true) {
            byteArrayOS.reset();
            byteArrayOS.write(reader, bufferLength / 2);

            if (byteArrayOS.size() == 0) {
                break;
            }

            dataOut.writeByte(mode);
            dataOut.writeInt(databaseID);
            dataOut.writeLong(sessionID);
            dataOut.writeLong(lobID);
            dataOut.writeInt(LobResultTypes.REQUEST_SET_CHARS);
            dataOut.writeLong(currentOffset);
            dataOut.writeLong(byteArrayOS.size() / 2);
            dataOut.write(byteArrayOS.getBuffer(), 0, byteArrayOS.size());

            currentOffset += byteArrayOS.size() / 2;

            if (byteArrayOS.size() < bufferLength) {
                break;
            }
        }
    }

    public long getLobID() {
        return lobID;
    }

    public int getSubType() {
        return subType;
    }

    public long getOffset() {
        return blockOffset;
    }

    public long getBlockLength() {
        return blockLength;
    }

    public byte[] getByteArray() {
        return byteBlock;
    }

    public char[] getCharArray() {
        return charBlock;
    }

    public InputStream getInputStream() {
        return stream;
    }

    public Reader getReader() {
        return reader;
    }
}
