/*
 * This file is a part of the SteamWar software.
 *
 * Copyright (C) 2023  SteamWar.de-Serverteam
 *
 * 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 <https://www.gnu.org/licenses/>.
 */

package de.steamwar.sql;

import de.steamwar.sql.internal.*;
import lombok.AllArgsConstructor;
import lombok.Getter;

import java.io.*;
import java.sql.PreparedStatement;
import java.util.zip.GZIPInputStream;

@AllArgsConstructor
@Getter
public class NodeData {

    static {
        new SqlTypeMapper<>(PipedInputStream.class, "BLOB", (rs, identifier) -> { throw new SecurityException("PipedInputStream is write only datatype"); }, PreparedStatement::setBinaryStream);
        new SqlTypeMapper<>(ByteArrayInputStream.class, "BLOB", (rs, identifier) -> { throw new SecurityException("ByteArrayInputStream is write only datatype"); }, PreparedStatement::setBinaryStream);
        new SqlTypeMapper<>(BufferedInputStream.class, "BLOB", (rs, identifier) -> { throw new SecurityException("BufferedInputStream is write only datatype"); }, PreparedStatement::setBinaryStream);

        SqlTypeMapper.ordinalEnumMapper(SchematicFormat.class);
    }

    private static final Table<NodeData> table = new Table<>(NodeData.class);

    private static final Statement updateDatabase = new Statement("INSERT INTO NodeData(NodeId, NodeFormat, SchemData) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE NodeFormat = VALUES(NodeFormat), SchemData = VALUES(SchemData)");
    private static final Statement selSchemData = new Statement("SELECT SchemData FROM NodeData WHERE NodeId = ?");

    private static final SelectStatement<NodeData> get = table.select(Table.PRIMARY);

    public static NodeData get(SchematicNode node) {
        if(node.isDir())
            throw new IllegalArgumentException("Node is a directory");
        return get.select(rs -> {
            if(rs.next()) {
                return new NodeData(node.getId(), SchematicFormat.values()[rs.getInt("NodeFormat")]);
            } else {
                return new NodeData(node.getId(), SchematicFormat.MCEDIT);
            }
        }, node);
    }

    @Field(keys = {Table.PRIMARY})
    private final int nodeId;

    @Field
    private SchematicFormat nodeFormat;

    public InputStream schemData() throws IOException {
        return schemData(true);
    }

    public InputStream schemData(boolean decompress) throws IOException {
        try {
            return selSchemData.select(rs -> {
                rs.next();
                InputStream schemData = rs.getBinaryStream("SchemData");
                try {
                    if(rs.wasNull() || schemData.available() == 0) {
                        throw new SecurityException("SchemData is null");
                    }
                    if (decompress) {
                        return new GZIPInputStream(schemData);
                    } else {
                        return schemData;
                    }
                } catch (IOException e) {
                    throw new SecurityException("SchemData is wrong", e);
                }
            }, nodeId);
        } catch (Exception e) {
            throw new IOException(e);
        }
    }

    public void saveFromStream(InputStream blob, SchematicFormat newFormat) {
        updateDatabase.update(nodeId, newFormat, blob);
        nodeFormat = newFormat;
    }

    @AllArgsConstructor
    @Getter
    public enum SchematicFormat {
        MCEDIT(".schematic"),
        SPONGE_V2(".schem"),
        SPONGE_V3(".schem");

        private final String fileEnding;
    }
}
