/*
 * Copyright 2017, TeamDev Ltd. All rights reserved.
 *
 * Redistribution and use in source and/or binary forms, with or without
 * modification, must retain the above copyright notice and the following
 * disclaimer.
 *
 * 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 THE COPYRIGHT
 * OWNER 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.spine3.server.storage.jdbc.util;

import com.google.protobuf.Message;
import org.spine3.Internal;
import org.spine3.server.aggregate.Aggregate;
import org.spine3.server.entity.Entity;
import org.spine3.server.reflect.Classes;
import org.spine3.server.storage.jdbc.DatabaseException;
import org.spine3.server.storage.jdbc.Sql;

import java.sql.PreparedStatement;
import java.sql.SQLException;

import static org.spine3.base.Stringifiers.idToString;
import static org.spine3.server.storage.jdbc.Sql.Type.BIGINT;
import static org.spine3.server.storage.jdbc.Sql.Type.INT;
import static org.spine3.server.storage.jdbc.Sql.Type.VARCHAR_255;

/**
 * Helps to work with {@link Entity} ID columns.
 *
 * @param <I> the type of {@link Entity} IDs
 * @author Alexander Litus
 */
@Internal
public abstract class IdColumnSetter<I> {

    /**
     * Creates a new instance.
     *
     * @param entityClass a class of an {@link Entity} or an {@link Aggregate}
     * @param <I>         the type of {@link Entity} IDs
     * @return a new helper instance
     */
    public static <I> IdColumnSetter<I> newInstance(Class<? extends Entity<I, ?>> entityClass) {
        final IdColumnSetter<I> helper;
        final Class<I> idClass =
                Classes.getGenericParameterType(entityClass, Entity.GenericParameter.ID.getIndex());
        if (idClass.equals(Long.class)) {
            @SuppressWarnings("unchecked") // is checked already
            final IdColumnSetter<I> longIdColumnSetter =
                    (IdColumnSetter<I>) new LongIdColumnSetter();
            helper = longIdColumnSetter;
        } else if (idClass.equals(Integer.class)) {
            @SuppressWarnings("unchecked") // is checked already
            final IdColumnSetter<I> intIdColumnSetter =
                    (IdColumnSetter<I>) new IntIdColumnSetter();
            helper = intIdColumnSetter;
        } else {
            helper = new StringOrMessageIdColumnSetter<>();
        }
        return helper;
    }

    /**
     * Returns the SQL data type string of the ID column, e.g. {@code "BIGINT"},
     * {@code "VARCHAR(999)"}, etc.
     */
    public abstract Sql.Type getColumnDataType();

    /**
     * Sets an ID parameter to the given value.
     *
     * @param index     the ID parameter index
     * @param id        the ID value to set
     * @param statement the statement to use
     * @throws DatabaseException if an error occurs during an interaction with the DB
     */
    public abstract void setId(int index, I id, PreparedStatement statement)
            throws DatabaseException;

    /**
     * Helps to work with columns which contain {@code long} {@link Entity} IDs.
     */
    private static class LongIdColumnSetter extends IdColumnSetter<Long> {

        @Override
        public Sql.Type getColumnDataType() {
            return BIGINT;
        }

        @Override
        public void setId(int index, Long id, PreparedStatement statement)
                throws DatabaseException {
            try {
                statement.setLong(index, id);
            } catch (SQLException e) {
                throw new DatabaseException(e);
            }
        }
    }

    /**
     * Helps to work with columns which contain {@code integer} {@link Entity} IDs.
     */
    private static class IntIdColumnSetter extends IdColumnSetter<Integer> {

        @Override
        public Sql.Type getColumnDataType() {
            return INT;
        }

        @Override
        public void setId(int index, Integer id, PreparedStatement statement)
                throws DatabaseException {
            try {
                statement.setInt(index, id);
            } catch (SQLException e) {
                throw new DatabaseException(e);
            }
        }
    }

    /**
     * Helps to work with columns which contain either {@link Message} or {@code string}
     * {@link Entity} IDs.
     */
    private static class StringOrMessageIdColumnSetter<I> extends IdColumnSetter<I> {

        @Override
        public Sql.Type getColumnDataType() {
            return VARCHAR_255;
        }

        @Override
        public void setId(int index, I id, PreparedStatement statement) throws DatabaseException {
            final String idString = idToString(id);
            try {
                statement.setString(index, idString);
            } catch (SQLException e) {
                throw new DatabaseException(e);
            }
        }
    }

    /**
     * Helps to work with columns which contain {@code string} {@link Entity} IDs.
     */
    public static class StringIdColumnSetter extends StringOrMessageIdColumnSetter<String> {
    }
}

@@ -1666,34 +1666,46 @@
 %0A/**%0A * 
-Helps to work wi
+A helper class for setting 
 th
+e
  %7B@link 
@@ -1719,16 +1719,354 @@
  ID 
-columns.
+into a %7B@link PreparedStatement%7Das a query%0A * parameter.%0A *%0A * %3Cp%3EDepending on what type ID is, %7B@linkplain #setId(int, Object, PreparedStatement) setId%7D method%0A * will call one of the setters:%0A * %3Cul%3E%0A *     %3Cli%3E%7B@link PreparedStatement#setInt%7D%0A *     %3Cli%3E%7B@link PreparedStatement#setLong%7D%0A *     %3Cli%3E%7B@link PreparedStatement#setString%7D%0A * %3C/ul%3E
 %0A *%0A
@@ -3389,36 +3389,32 @@
 the 
-SQL data type string
+%7B@link Sql.Type%7D
  of the 
 ID c
@@ -3413,76 +3413,52 @@
 the 
-ID 
 column
-, e.g. %7B@code %22BIGINT%22%7D,%0A     * %7B@code %22VARCHAR(999)%22%7D, etc
+ with which this helper instance works
 .%0A  

Improve `IdColumnSetter` docs.