/**
 * 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.zeppelin.postgresql;

import static com.google.common.collect.Sets.newHashSet;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

import java.io.IOException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;

import jline.console.completer.Completer;

import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Joiner;
import com.google.common.collect.Sets;
import com.mockrunner.jdbc.BasicJDBCTestCaseAdapter;

public class SqlCompleterTest extends BasicJDBCTestCaseAdapter {

  private Logger logger = LoggerFactory.getLogger(SqlCompleterTest.class);

  private final static Set<String> EMPTY = new HashSet<String>();

  private CompleterTester tester;

  private SqlCompleter sqlCompleter;

  @Before
  public void beforeTest() throws IOException, SQLException {
    Set<String> keywordsCompletions =
        SqlCompleter.getSqlKeywordsCompletions(getJDBCMockObjectFactory().getMockConnection());
    Set<String> dataModelCompletions =
        SqlCompleter
            .getDataModelMetadataCompletions(getJDBCMockObjectFactory().getMockConnection());

    sqlCompleter =
        new SqlCompleter(Sets.union(keywordsCompletions, dataModelCompletions),
            dataModelCompletions);
    tester = new CompleterTester(sqlCompleter);
  }

  @Test
  public void testAfterBufferEnd() {
    String buffer = "ORDER";
    // Up to 2 white spaces after the buffer end, the completer still uses the last argument
    tester.buffer(buffer).from(0).to(buffer.length() + 1).expect(newHashSet("ORDER ")).test();
    // 2 white spaces or more behind the buffer end the completer returns empty result
    tester.buffer(buffer).from(buffer.length() + 2).to(buffer.length() + 5).expect(EMPTY).test();
  }

  @Test
  public void testEdges() {
    String buffer = "  ORDER  ";
    tester.buffer(buffer).from(0).to(8).expect(newHashSet("ORDER ")).test();
    tester.buffer(buffer).from(9).to(15).expect(EMPTY).test();
  }

  @Test
  public void testMultipleWords() {
    String buffer = "  SELE  fro    LIM";
    tester.buffer(buffer).from(0).to(6).expect(newHashSet("SELECT ")).test();
    tester.buffer(buffer).from(7).to(11).expect(newHashSet("from ")).test();
    tester.buffer(buffer).from(12).to(19).expect(newHashSet("LIMIT ")).test();
    tester.buffer(buffer).from(20).to(24).expect(EMPTY).test();
  }

  @Test
  public void testMultiLineBuffer() {
    String buffer = " \n SELE \n fro";
    tester.buffer(buffer).from(0).to(7).expect(newHashSet("SELECT ")).test();
    tester.buffer(buffer).from(8).to(14).expect(newHashSet("from ")).test();
    tester.buffer(buffer).from(15).to(17).expect(EMPTY).test();
  }

  @Test
  public void testMultipleCompletionSuggestions() {
    String buffer = "  SU";
    tester.buffer(buffer).from(0).to(5).expect(newHashSet("SUBCLASS_ORIGIN", "SUM", "SUBSTRING"))
        .test();
    tester.buffer(buffer).from(6).to(7).expect(EMPTY).test();
  }

  @Test
  public void testDotDelimiter() {
    String buffer = "  order.select  ";
    tester.buffer(buffer).from(4).to(7).expect(newHashSet("order ")).test();
    tester.buffer(buffer).from(8).to(15).expect(newHashSet("select ")).test();
    tester.buffer(buffer).from(16).to(17).expect(EMPTY).test();
  }

  @Test
  public void testSqlDelimiterCharacters() {
    assertTrue(sqlCompleter.getSqlDelimiter().isDelimiterChar("r.", 1));
    assertTrue(sqlCompleter.getSqlDelimiter().isDelimiterChar("SS;", 2));
    assertTrue(sqlCompleter.getSqlDelimiter().isDelimiterChar(":", 0));
    assertTrue(sqlCompleter.getSqlDelimiter().isDelimiterChar("ttt,", 3));
  }

  public class CompleterTester {

    private Completer completer;

    private String buffer;
    private int fromCursor;
    private int toCursor;
    private Set<String> expectedCompletions;

    public CompleterTester(Completer completer) {
      this.completer = completer;
    }

    public CompleterTester buffer(String buffer) {
      this.buffer = buffer;
      return this;
    }

    public CompleterTester from(int fromCursor) {
      this.fromCursor = fromCursor;
      return this;
    }

    public CompleterTester to(int toCursor) {
      this.toCursor = toCursor;
      return this;
    }

    public CompleterTester expect(Set<String> expectedCompletions) {
      this.expectedCompletions = expectedCompletions;
      return this;
    }

    public void test() {
      for (int c = fromCursor; c <= toCursor; c++) {
        expectedCompletions(buffer, c, expectedCompletions);
      }
    }

    private void expectedCompletions(String buffer, int cursor, Set<String> expected) {

      ArrayList<CharSequence> candidates = new ArrayList<CharSequence>();

      completer.complete(buffer, cursor, candidates);

      String explain = explain(buffer, cursor, candidates);

      logger.info(explain);

      assertEquals("Buffer [" + buffer.replace(" ", ".") + "] and Cursor[" + cursor + "] "
          + explain, expected, newHashSet(candidates));
    }

    private String explain(String buffer, int cursor, ArrayList<CharSequence> candidates) {
      StringBuffer sb = new StringBuffer();

      for (int i = 0; i <= Math.max(cursor, buffer.length()); i++) {
        if (i == cursor) {
          sb.append("(");
        }
        if (i >= buffer.length()) {
          sb.append("_");
        } else {
          if (Character.isWhitespace(buffer.charAt(i))) {
            sb.append(".");
          } else {
            sb.append(buffer.charAt(i));
          }
        }
        if (i == cursor) {
          sb.append(")");
        }
      }
      sb.append(" >> [").append(Joiner.on(",").join(candidates)).append("]");

      return sb.toString();
    }
  }
}
