package it.unibz.inf.ontop.generation.serializer.impl;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import it.unibz.inf.ontop.dbschema.*;
import it.unibz.inf.ontop.generation.algebra.SQLValuesExpression;
import it.unibz.inf.ontop.generation.algebra.SelectFromWhereWithModifiers;
import it.unibz.inf.ontop.generation.serializer.SelectFromWhereSerializer;
import it.unibz.inf.ontop.model.term.TermFactory;
import it.unibz.inf.ontop.model.term.Variable;
import it.unibz.inf.ontop.utils.ImmutableCollectors;

import java.util.stream.Collectors;

@Singleton
public class H2SelectFromWhereSerializer extends DefaultSelectFromWhereSerializer implements SelectFromWhereSerializer {

    @Inject
    private H2SelectFromWhereSerializer(TermFactory termFactory) {
        super(new DefaultSQLTermSerializer(termFactory));
    }

    @Override
    public SelectFromWhereSerializer.QuerySerialization serialize(SelectFromWhereWithModifiers selectFromWhere, DBParameters dbParameters) {
        return selectFromWhere.acceptVisitor(
                new DefaultRelationVisitingSerializer(dbParameters.getQuotedIDFactory()) {
                    /**
                     * Number of rows in output can be limited either with standard OFFSET / FETCH,
                     * with non-standard LIMIT / OFFSET, or with non-standard TOP clauses.
                     * Different clauses cannot be used together. OFFSET specifies how many rows to skip.
                     * Please note that queries with high offset values can be slow.
                     * FETCH FIRST/NEXT, LIMIT or TOP limits the number of rows returned by the query
                     * (no limit if null or smaller than zero). If PERCENT is specified number of rows
                     * is specified as a percent of the total number of rows and should be an integer
                     * value between 0 and 100 inclusive. WITH TIES can be used only together with
                     * ORDER BY and means that all additional rows that have the same sorting position
                     * as the last row will be also returned.
                     *
                     * LIMIT expression [OFFSET expression]
                     * OFFSET expression ROW|ROWS FETCH FIRST|(NEXT expression) [PERCENT]
                     */

                    // serializeLimit is standard

                    @Override
                    protected String serializeLimitOffset(long limit, long offset, boolean noSortCondition) {
                        return String.format("OFFSET %d ROWS\nFETCH NEXT %d ROWS ONLY", offset, limit);
                    }

                    @Override
                    protected String serializeOffset(long offset, boolean noSortCondition) {
                        return String.format("OFFSET %d ROWS", offset);
                    }

                    @Override
                    public QuerySerialization visit(SQLValuesExpression sqlValuesExpression) {

                        // H2 version 2+ and above aligned with other SQL dialects
                        // H2 version 1 requires special aliasing conventions
                        if (dbParameters.getDbmsVersion().startsWith("1")) {
                            // Leaf node
                            ImmutableMap<Variable, QualifiedAttributeID> childColumnIDs = ImmutableMap.of();

                            String tuplesSerialized = sqlValuesExpression.getValues().stream()
                                    .map(tuple -> tuple.stream()
                                            .map(constant -> sqlTermSerializer.serialize(constant, childColumnIDs))
                                            .collect(Collectors.joining(",", " (", ")")))
                                    .collect(Collectors.joining(","));

                            RelationID alias = generateFreshViewAlias();

                            String sql = "(VALUES " + tuplesSerialized + " AS " + alias + " )";

                            ImmutableList<Variable> orderedVariables = sqlValuesExpression.getOrderedVariables();

                            // Map the column names generated by VALUES in h2 to projected variables
                            ImmutableMap<Variable, QualifiedAttributeID> columnIDs = orderedVariables.stream()
                                    .collect(ImmutableCollectors.toMap(
                                            variable -> variable,
                                            variable -> new QualifiedAttributeID(null, idFactory.createAttributeID(
                                                    alias + ".C" + (orderedVariables.indexOf(variable) + 1)))));

                            return new QuerySerializationImpl(sql, columnIDs);
                        } else {
                            return super.visit(sqlValuesExpression);
                        }
                    }

                });
    }
}
