/*
 * Copyright 2016, 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.storage.jdbc.DatabaseException;

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

import static org.spine3.base.Identifiers.idToString;

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

    private static final String TYPE_VARCHAR = "VARCHAR(999)";

    private static final String TYPE_BIGINT = "BIGINT";

    private static final String TYPE_INT = "INT";

    /**
     * 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> IdColumn<I> newInstance(Class<? extends Entity<I, ?>> entityClass) {
        final IdColumn<I> helper;
        final Class<I> idClass = Entity.getIdClass(entityClass);
        if (idClass.equals(Long.class)) {
            @SuppressWarnings("unchecked") // is checked already
            final IdColumn<I> longIdColumn = (IdColumn<I>) new LongIdColumn();
            helper = longIdColumn;
        } else if (idClass.equals(Integer.class)) {
            @SuppressWarnings("unchecked") // is checked already
            final IdColumn<I> intIdColumn = (IdColumn<I>) new IntIdColumn();
            helper = intIdColumn;
        } else {
            helper = new StringOrMessageIdColumn<>();
        }
        return helper;
    }

    /** Returns the SQL data type string of the ID column, e.g. {@code "BIGINT"}, {@code "VARCHAR(999)"}, etc. */
    public abstract String 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 LongIdColumn extends IdColumn<Long> {

        @Override
        public String getColumnDataType() {
            return TYPE_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 IntIdColumn extends IdColumn<Integer> {

        @Override
        public String getColumnDataType() {
            return TYPE_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 StringOrMessageIdColumn<I> extends IdColumn<I> {

        @Override
        public String getColumnDataType() {
            return TYPE_VARCHAR;
        }

        @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 StringIdColumn extends StringOrMessageIdColumn<String> {
    }
}

@@ -1383,16 +1383,204 @@
 oString;
+%0Aimport static org.spine3.server.storage.jdbc.Sql.Type.BIGINT;%0Aimport static org.spine3.server.storage.jdbc.Sql.Type.INT;%0Aimport static org.spine3.server.storage.jdbc.Sql.Type.VARCHAR_999;
 %0A%0A/**%0A *
@@ -1756,180 +1756,8 @@
  %7B%0A%0A
-    private static final String TYPE_VARCHAR = %22VARCHAR(999)%22;%0A%0A    private static final String TYPE_BIGINT = %22BIGINT%22;%0A%0A    private static final String TYPE_INT = %22INT%22;%0A%0A
     
@@ -1886,32 +1886,40 @@
     * @param %3CI%3E
+        
  the type of %7B@l
@@ -2736,16 +2736,23 @@
 %0A    /**
+%0A     *
  Returns
@@ -2846,16 +2846,21 @@
 %22%7D, etc.
+%0A    
  */%0A    
@@ -3300,32 +3300,39 @@
 eption;%0A%0A    /**
+%0A     *
  Helps to work w
@@ -3381,32 +3381,37 @@
 ink Entity%7D IDs.
+%0A    
  */%0A    private 
@@ -3547,19 +3547,25 @@
 urn 
-TYPE_
 BIGINT
+.toString()
 ;%0A  
@@ -3874,32 +3874,39 @@
 %7D%0A    %7D%0A%0A    /**
+%0A     *
  Helps to work w
@@ -3958,32 +3958,37 @@
 ink Entity%7D IDs.
+%0A    
  */%0A    private 
@@ -4126,16 +4126,22 @@
 urn 
-TYPE_INT
+INT.toString()
 ;%0A  
@@ -4452,32 +4452,39 @@
 %7D%0A    %7D%0A%0A    /**
+%0A     *
  Helps to work w
@@ -4561,32 +4561,37 @@
 ink Entity%7D IDs.
+%0A    
  */%0A    private 
@@ -4738,20 +4738,30 @@
 urn 
-TYPE_
 VARCHAR
+_999.toString()
 ;%0A  
@@ -5135,16 +5135,23 @@
 %0A    /**
+%0A     *
  Helps t
@@ -5218,16 +5218,21 @@
 ty%7D IDs.
+%0A    
  */%0A    

Remove duplicate string usage.