/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.ignite.internal.schema.configuration;

import java.util.Collection;
import java.util.Collections;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
import org.apache.ignite.configuration.ConfigurationRegistry;
import org.apache.ignite.configuration.schemas.table.TableConfiguration;
import org.apache.ignite.configuration.schemas.table.TableValidator;
import org.apache.ignite.configuration.schemas.table.TablesConfiguration;
import org.apache.ignite.schema.ColumnType;
import org.apache.ignite.schema.HashIndex;
import org.apache.ignite.schema.PartialIndex;
import org.apache.ignite.schema.PrimaryIndex;
import org.apache.ignite.schema.SchemaBuilders;
import org.apache.ignite.schema.SchemaTable;
import org.apache.ignite.schema.SortedIndex;
import org.apache.ignite.schema.SortedIndexColumn;
import org.apache.ignite.schema.TableIndex;
import org.apache.ignite.schema.builder.HashIndexBuilder;
import org.apache.ignite.schema.builder.PartialIndexBuilder;
import org.apache.ignite.schema.builder.PrimaryIndexBuilder;
import org.apache.ignite.schema.builder.SchemaTableBuilder;
import org.apache.ignite.schema.builder.SortedIndexBuilder;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;

/**
 * SchemaConfigurationConverter tests.
 */
@SuppressWarnings("InstanceVariableMayNotBeInitialized")
public class SchemaConfigurationConverterTest {
    /** Table builder. */
    private SchemaTableBuilder tblBuilder;

    /** Configuration registry with one table for each test. */
    private ConfigurationRegistry confRegistry;

    /**
     * Prepare configuration registry for test.
     *
     * @throws ExecutionException If failed.
     * @throws InterruptedException If failed.
     */
    @BeforeEach
    public void createRegistry() throws ExecutionException, InterruptedException {
        confRegistry = new ConfigurationRegistry(
            Collections.singleton(TablesConfiguration.KEY),
            Collections.singletonMap(TableValidator.class, Collections.singleton(SchemaTableValidatorImpl.INSTANCE)),
            Collections.singleton(new TestConfigurationStorage()));

        tblBuilder = SchemaBuilders.tableBuilder("SNAME","TNAME")
            .columns(
                SchemaBuilders.column("COL1", ColumnType.DOUBLE).build(),
                SchemaBuilders.column("COL2", ColumnType.DOUBLE).build(),
                SchemaBuilders.column("A", ColumnType.INT8).build(),
                SchemaBuilders.column("B", ColumnType.INT8).build(),
                SchemaBuilders.column("C", ColumnType.INT8).build()
            ).withPrimaryKey("COL1");

        SchemaTable tbl = tblBuilder.build();

        confRegistry.getConfiguration(TablesConfiguration.KEY).change(
            ch -> {
                SchemaConfigurationConverter.createTable(tbl, ch)
                    .changeTables(tblsCh -> tblsCh.create(tbl.canonicalName(),
                        tblCh -> tblCh.changeReplicas(1)));
            }).get();
    }

    /**
     * Add/remove HashIndex into configuration and read it back.
     */
    @Test
    public void testConvertHashIndex() throws ExecutionException, InterruptedException {
        HashIndexBuilder builder = SchemaBuilders.hashIndex("testHI")
            .withColumns("A", "B", "C")
            .withHints(Collections.singletonMap("param","value"));
        HashIndex idx = builder.build();

        getTbl().change(ch -> SchemaConfigurationConverter.addIndex(idx, ch)).get();

        SchemaTable tbl = SchemaConfigurationConverter.convert(getTbl().value());

        HashIndex idx2 = (HashIndex)getIdx(idx.name(), tbl.indices());

        assertNotNull(idx2);
        assertEquals("HASH", idx2.type());
        assertEquals(3, idx2.columns().size());
    }

    /**
     * Add/remove SortedIndex into configuration and read it back.
     */
    @Test
    public void testConvertSortedIndex() throws ExecutionException, InterruptedException {
        SortedIndexBuilder builder = SchemaBuilders.sortedIndex("SIDX");

        builder.addIndexColumn("A").asc().done();
        builder.addIndexColumn("B").desc().done();

        builder.unique();

        SortedIndex idx = builder.build();

        getTbl().change(ch -> SchemaConfigurationConverter.addIndex(idx, ch)).get();

        SchemaTable tbl = SchemaConfigurationConverter.convert(getTbl().value());

        SortedIndex idx2 = (SortedIndex)getIdx(idx.name(), tbl.indices());

        assertNotNull(idx2);
        assertEquals("SORTED", idx2.type());
        assertEquals(2, idx2.columns().size());
    }

    /**
     * Add/remove PrimaryIndex into configuration and read it back.
     */
    @Test
    public void testPKIndex() throws ExecutionException, InterruptedException {
        PrimaryIndexBuilder builder = SchemaBuilders.pkIndex();
        builder.addIndexColumn("COL1").desc().done();
        builder.addIndexColumn("A").desc().done();
        builder.addIndexColumn("B").asc().done();
        builder.withAffinityColumns("COL1");

        PrimaryIndex idx = builder.build();

        getTbl().change(ch -> SchemaConfigurationConverter.addIndex(idx, ch)).get();

        SchemaTable tbl = SchemaConfigurationConverter.convert(getTbl().value());

        PrimaryIndex idx2 = (PrimaryIndex)getIdx(idx.name(), tbl.indices());

        assertNotNull(idx2);
        assertEquals("PK", idx2.type());
        assertEquals(idx.columns().stream().map(SortedIndexColumn::name).collect(Collectors.toList()),
            idx2.columns().stream().map(SortedIndexColumn::name).collect(Collectors.toList()));
        assertEquals(idx.affinityColumns(), idx2.affinityColumns());
    }

    /**
     * Add/remove PartialIndex into configuration and read it back.
     */
    @Test
    public void testPartialIndex() throws ExecutionException, InterruptedException {
        PartialIndexBuilder builder = SchemaBuilders.partialIndex("TEST");

        builder.addIndexColumn("A").done();
        builder.withExpression("WHERE A > 0");

        PartialIndex idx = builder.build();

        getTbl().change(ch -> SchemaConfigurationConverter.addIndex(idx, ch)).get();

        SchemaTable tbl = SchemaConfigurationConverter.convert(getTbl().value());

        PartialIndex idx2 = (PartialIndex) getIdx(idx.name(), tbl.indices());

        assertNotNull(idx2);
        assertEquals("PARTIAL", idx2.type());
        assertEquals(idx.columns().size(), idx2.columns().size());
    }

    /**
     * Add/remove table and read it back.
     */
    @Test
    public void testConvertTable() {
        SchemaTable tbl = tblBuilder.build();

        TableConfiguration tblCfg = confRegistry.getConfiguration(TablesConfiguration.KEY).tables()
            .get(tbl.canonicalName());

        SchemaTable tbl2 = SchemaConfigurationConverter.convert(tblCfg);

        assertEquals(tbl.canonicalName(), tbl2.canonicalName());
        assertEquals(tbl.indices().size(), tbl2.indices().size());
        assertEquals(tbl.keyColumns().size(), tbl2.keyColumns().size());
        assertEquals(tbl.affinityColumns().size(), tbl2.affinityColumns().size());
        assertEquals(tbl.valueColumns().size(), tbl2.valueColumns().size());
    }

    /**
     * Get tests default table configuration.
     *
     * @return Configuration of default table.
     */
    private TableConfiguration getTbl() {
        return confRegistry.getConfiguration(TablesConfiguration.KEY).tables().get(tblBuilder.build().canonicalName());
    }

    /**
     * Get table index by name.
     *
     * @param name Index name to find.
     * @param idxs Table indexes.
     * @return Index or {@code null} if there are no index with such name.
     */
    private TableIndex getIdx(String name, Collection<TableIndex> idxs) {
        return idxs.stream().filter(idx -> name.equals(idx.name())).findAny().orElse(null);
    }
}
