/*
 * Copyright 2004-2023 H2 Group. Multiple-Licensed under the MPL 2.0,
 * and the EPL 1.0 (https://h2database.com/html/license.html).
 * Initial Developer: H2 Group
 */
package org.h2.test.unit;

import java.sql.Connection;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.h2.bnf.Bnf;
import org.h2.bnf.context.DbContents;
import org.h2.bnf.context.DbContextRule;
import org.h2.bnf.context.DbProcedure;
import org.h2.bnf.context.DbSchema;
import org.h2.test.TestBase;
import org.h2.test.TestDb;

/**
 * Test Bnf Sql parser
 * @author Nicolas Fortin
 */
public class TestBnf extends TestDb {

    /**
     * Run just this test.
     *
     * @param a ignored
     */
    public static void main(String... a) throws Exception {
        TestBase.createCaller().init().testFromMain();
    }

    @Override
    public void test() throws Exception {
        deleteDb("bnf");
        try (Connection conn = getConnection("bnf")) {
            testModes(conn);
            testProcedures(conn, false);
        }
        deleteDb("bnf");
        try (Connection conn = getConnection("bnf;mode=mysql;database_to_lower=true")) {
            testProcedures(conn, true);
        }
    }

    private void testModes(Connection conn) throws Exception {
        DbContents dbContents;
        dbContents = new DbContents();
        dbContents.readContents("jdbc:h2:./test", conn);
        assertTrue(dbContents.isH2());
        dbContents = new DbContents();
        dbContents.readContents("jdbc:derby:test", conn);
        assertTrue(dbContents.isDerby());
        dbContents = new DbContents();
        dbContents.readContents("jdbc:firebirdsql:test", conn);
        assertTrue(dbContents.isFirebird());
        dbContents = new DbContents();
        dbContents.readContents("jdbc:sqlserver:test", conn);
        assertTrue(dbContents.isMSSQLServer());
        dbContents = new DbContents();
        dbContents.readContents("jdbc:mysql:test", conn);
        assertTrue(dbContents.isMySQL());
        dbContents = new DbContents();
        dbContents.readContents("jdbc:oracle:test", conn);
        assertTrue(dbContents.isOracle());
        dbContents = new DbContents();
        dbContents.readContents("jdbc:postgresql:test", conn);
        assertTrue(dbContents.isPostgreSQL());
        dbContents = new DbContents();
        dbContents.readContents("jdbc:sqlite:test", conn);
        assertTrue(dbContents.isSQLite());
    }

    private void testProcedures(Connection conn, boolean isMySQLMode)
            throws Exception {
        // Register a procedure and check if it is present in DbContents
        conn.createStatement().execute(
                "DROP ALIAS IF EXISTS CUSTOM_PRINT");
        conn.createStatement().execute(
                "CREATE ALIAS CUSTOM_PRINT " +
                "AS $$ void print(String s) { System.out.println(s); } $$");
        conn.createStatement().execute(
                "DROP TABLE IF EXISTS " +
                "TABLE_WITH_STRING_FIELD");
        conn.createStatement().execute(
                "CREATE TABLE " +
                "TABLE_WITH_STRING_FIELD (STRING_FIELD VARCHAR(50), INT_FIELD integer)");
        DbContents dbContents = new DbContents();
        dbContents.readContents("jdbc:h2:./test", conn);
        assertTrue(dbContents.isH2());
        assertFalse(dbContents.isDerby());
        assertFalse(dbContents.isFirebird());
        assertEquals(null, dbContents.quoteIdentifier(null));
        if (isMySQLMode) {
            assertEquals("\"TEST\"", dbContents.quoteIdentifier("TEST"));
            assertEquals("\"Test\"", dbContents.quoteIdentifier("Test"));
            assertEquals("test", dbContents.quoteIdentifier("test"));
        } else {
            assertEquals("TEST", dbContents.quoteIdentifier("TEST"));
            assertEquals("\"Test\"", dbContents.quoteIdentifier("Test"));
            assertEquals("\"test\"", dbContents.quoteIdentifier("test"));
        }
        assertFalse(dbContents.isMSSQLServer());
        assertFalse(dbContents.isMySQL());
        assertFalse(dbContents.isOracle());
        assertFalse(dbContents.isPostgreSQL());
        assertFalse(dbContents.isSQLite());
        DbSchema defaultSchema = dbContents.getDefaultSchema();
        DbProcedure[] procedures = defaultSchema.getProcedures();
        Set<String> procedureName = new HashSet<>(procedures.length);
        for (DbProcedure procedure : procedures) {
            assertTrue(defaultSchema == procedure.getSchema());
            procedureName.add(procedure.getName());
        }
        if (isMySQLMode) {
            assertTrue(procedureName.contains("custom_print"));
        } else {
            assertTrue(procedureName.contains("CUSTOM_PRINT"));
        }

        if (isMySQLMode) {
            return;
        }

        // Test completion
        Bnf bnf = Bnf.getInstance(null);
        DbContextRule columnRule = new
                DbContextRule(dbContents, DbContextRule.COLUMN);
        bnf.updateTopic("column_name", columnRule);
        bnf.updateTopic("user_defined_function_name", new
                DbContextRule(dbContents, DbContextRule.PROCEDURE));
        bnf.linkStatements();
        // Test partial
        Map<String, String> tokens;
        tokens = bnf.getNextTokenList("SELECT CUSTOM_PR");
        assertTrue(tokens.values().contains("INT"));

        // Test identifiers are working
        tokens = bnf.getNextTokenList("create table \"test\" as (s" + "el");
        assertTrue(tokens.values().contains("E" + "CT"));

        tokens = bnf.getNextTokenList("create table test as (s" + "el");
        assertTrue(tokens.values().contains("E" + "CT"));

        // Test || with and without spaces
        tokens = bnf.getNextTokenList("select 1||f");
        assertFalse(tokens.values().contains("R" + "OM"));
        tokens = bnf.getNextTokenList("select 1 || f");
        assertFalse(tokens.values().contains("R" + "OM"));
        tokens = bnf.getNextTokenList("select 1 || 2 ");
        assertTrue(tokens.values().contains("FROM"));
        tokens = bnf.getNextTokenList("select 1||2");
        assertTrue(tokens.values().contains("FROM"));
        tokens = bnf.getNextTokenList("select 1 || 2");
        assertTrue(tokens.values().contains("FROM"));

        // Test keyword
        tokens = bnf.getNextTokenList("SELECT LE" + "AS");
        assertTrue(tokens.values().contains("T"));

        // Test parameters
        tokens = bnf.getNextTokenList("SELECT CUSTOM_PRINT(");
        assertTrue(tokens.values().contains("STRING_FIELD"));
        assertFalse(tokens.values().contains("INT_FIELD"));

        // Test parameters with spaces
        tokens = bnf.getNextTokenList("SELECT CUSTOM_PRINT ( ");
        assertTrue(tokens.values().contains("STRING_FIELD"));
        assertFalse(tokens.values().contains("INT_FIELD"));

        // Test parameters with close bracket
        tokens = bnf.getNextTokenList("SELECT CUSTOM_PRINT ( STRING_FIELD");
        assertTrue(tokens.values().contains(")"));
    }
}