/*
 * Copyright (C) 2026  Tim Zimmermann <tim@aglais.org>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.aglais.zms.db.core.model;

import java.io.*;
import java.sql.Blob;
import java.sql.SQLException;
import java.util.Arrays;

/**
 * A custom {@link Blob} implementation not depending on any javax.sql classes that only supports byte arrays.
 */
public class ByteArrayBlob implements Blob, Serializable {

    @Serial
    private static final long serialVersionUID = 1;

    private byte[] content;

    /**
     * Create a new {@link ByteArrayBlob} object
     *
     * @param content The content buffer
     */
    public ByteArrayBlob(byte[] content) {
        this.content = Arrays.copyOf(content, content.length);
    }

    @Override
    public long length() throws SQLException {
        return content.length;
    }

    @Override
    public byte[] getBytes(long pos, int length) {
        if (length > content.length) {
            length = content.length;
        }

        return Arrays.copyOfRange(content, (int) pos - 1, length);
    }

    @Override
    public InputStream getBinaryStream() {
        return new ByteArrayInputStream(content);
    }

    @Override
    public long position(byte[] pattern, long start) {
        if (start < 1 || start > content.length) {
            return -1;
        }

        int i = 0;
        for (int pos = (int) start - 1; pos < content.length; pos++) {
            if (content[pos] == pattern[i]) {
                i++;

                if (i == pattern.length) {
                    return (pos + 1) - (pattern.length - 1);
                }
            } else {
                i = 0;
            }
        }

        return -1;
    }

    @Override
    public long position(Blob pattern, long start) throws SQLException {
        return position(pattern.getBytes(1, (int) pattern.length()), start);
    }

    @Override
    public int setBytes(long pos, byte[] bytes) {
        return setBytes(pos, bytes, 0, bytes.length);
    }

    @Override
    public int setBytes(long pos, byte[] bytes, int offset, int length) {
        int i;
        for (i = 0; i < content.length && (offset + i < bytes.length); i++) {
            content[(int) pos - 1 + i] = bytes[offset + i];
        }

        return i;
    }

    @Override
    public OutputStream setBinaryStream(long pos) {
        throw new UnsupportedOperationException();
    }

    @Override
    public void truncate(long length) {
        content = getBytes(1, (int) length);
    }

    @Override
    public void free() {
        content = null;
    }

    @Override
    public InputStream getBinaryStream(long pos, long length) {
        return new ByteArrayInputStream(content, (int) pos - 1, (int) length);
    }

    @Override
    public int hashCode() {
        return ((31 + Arrays.hashCode(content)) * 31 + content.length) * 31;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof ByteArrayBlob blob) {
            return Arrays.equals(content, blob.content);
        }

        return false;
    }
}
