/*
 * 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.phoenix.query;

import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import java.sql.Connection;
import java.sql.Date;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;

import org.apache.hadoop.hbase.KeyValue;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.hbase.util.Pair;
import org.apache.phoenix.exception.SQLExceptionCode;
import org.apache.phoenix.jdbc.PhoenixConnection;
import org.apache.phoenix.jdbc.PhoenixDatabaseMetaData;
import org.apache.phoenix.jdbc.PhoenixDriver;
import org.apache.phoenix.schema.types.PChar;
import org.apache.phoenix.schema.types.PDate;
import org.apache.phoenix.schema.types.PVarchar;
import org.apache.phoenix.schema.SaltingUtil;
import org.apache.phoenix.util.ByteUtil;
import org.apache.phoenix.util.PhoenixRuntime;
import org.apache.phoenix.util.StringUtil;
import org.junit.BeforeClass;
import org.junit.Test;


public class ConnectionlessTest {
    private static final int saltBuckets = 200;
    private static final String orgId = "00D300000000XHP";
    private static final String keyPrefix1 = "111";
    private static final String keyPrefix2 = "112";
    private static final String entityHistoryId1 = "123456789012";
    private static final String entityHistoryId2 = "987654321098";
    private static final String name1 = "Eli";
    private static final String name2 = "Simon";
    private static final Date now = new Date(System.currentTimeMillis());
    private static final byte[] unsaltedRowKey1 = ByteUtil.concat(
            PChar.INSTANCE.toBytes(orgId), PChar.INSTANCE.toBytes(keyPrefix1), PChar.INSTANCE.toBytes(entityHistoryId1));
    private static final byte[] unsaltedRowKey2 = ByteUtil.concat(
            PChar.INSTANCE.toBytes(orgId), PChar.INSTANCE.toBytes(keyPrefix2), PChar.INSTANCE.toBytes(entityHistoryId2));
    private static final byte[] saltedRowKey1 = ByteUtil.concat(
            new byte[] {SaltingUtil.getSaltingByte(unsaltedRowKey1, 0, unsaltedRowKey1.length, saltBuckets)},
            unsaltedRowKey1);
    private static final byte[] saltedRowKey2 = ByteUtil.concat(
            new byte[] {SaltingUtil.getSaltingByte(unsaltedRowKey2, 0, unsaltedRowKey2.length, saltBuckets)},
            unsaltedRowKey2);

    private static String getUrl() {
        return PhoenixRuntime.JDBC_PROTOCOL + PhoenixRuntime.JDBC_PROTOCOL_SEPARATOR + PhoenixRuntime.CONNECTIONLESS;
    }
    
    @BeforeClass
    public static void verifyDriverRegistered() throws SQLException {
        assertTrue(DriverManager.getDriver(getUrl()) == PhoenixDriver.INSTANCE);
    }
    
    @Test
    public void testConnectionlessUpsert() throws Exception {
        testConnectionlessUpsert(null);
    }
    
    @Test
    public void testSaltedConnectionlessUpsert() throws Exception {
        testConnectionlessUpsert(saltBuckets);
    }
  
    private void testConnectionlessUpsert(Integer saltBuckets) throws Exception {
        String dmlStmt = "create table core.entity_history(\n" +
        "    organization_id char(15) not null, \n" + 
        "    key_prefix char(3) not null,\n" +
        "    entity_history_id char(12) not null,\n" + 
        "    created_by varchar,\n" + 
        "    created_date date\n" +
        "    CONSTRAINT pk PRIMARY KEY (organization_id, key_prefix, entity_history_id) ) " +
        (saltBuckets == null ? "" : (PhoenixDatabaseMetaData.SALT_BUCKETS + "=" + saltBuckets));
        Properties props = new Properties();
        Connection conn = DriverManager.getConnection(getUrl(), props);
        PreparedStatement statement = conn.prepareStatement(dmlStmt);
        statement.execute();
        
        String upsertStmt = "upsert into core.entity_history(organization_id,key_prefix,entity_history_id, created_by, created_date)\n" +
        "values(?,?,?,?,?)";
        statement = conn.prepareStatement(upsertStmt);
        statement.setString(1, orgId);
        statement.setString(2, keyPrefix2);
        statement.setString(3, entityHistoryId2);
        statement.setString(4, name2);
        statement.setDate(5,now);
        statement.execute();
        statement.setString(1, orgId);
        statement.setString(2, keyPrefix1);
        statement.setString(3, entityHistoryId1);
        statement.setString(4, name1);
        statement.setDate(5,now);
        statement.execute();
        
        Iterator<Pair<byte[],List<KeyValue>>> dataIterator = PhoenixRuntime.getUncommittedDataIterator(conn);
        Iterator<KeyValue> iterator = dataIterator.next().getSecond().iterator();

        byte[] expectedRowKey1 = saltBuckets == null ? unsaltedRowKey1 : saltedRowKey1;
        byte[] expectedRowKey2 = saltBuckets == null ? unsaltedRowKey2 : saltedRowKey2;
        if (Bytes.compareTo(expectedRowKey1, expectedRowKey2) < 0) {
            assertRow1(iterator, expectedRowKey1);
            assertRow2(iterator, expectedRowKey2);
        } else {
            assertRow2(iterator, expectedRowKey2);
            assertRow1(iterator, expectedRowKey1);
        }
        
        assertFalse(iterator.hasNext());
        assertFalse(dataIterator.hasNext());
        conn.rollback(); // to clear the list of mutations for the next
    }
    
    @SuppressWarnings("deprecation")
    private static void assertRow1(Iterator<KeyValue> iterator, byte[] expectedRowKey1) {
        KeyValue kv;
        assertTrue(iterator.hasNext());
        kv = iterator.next();
        assertArrayEquals(expectedRowKey1, kv.getRow());        
        assertEquals(name1, PVarchar.INSTANCE.toObject(kv.getValue()));
        assertTrue(iterator.hasNext());
        kv = iterator.next();
        assertArrayEquals(expectedRowKey1, kv.getRow());        
        assertEquals(now, PDate.INSTANCE.toObject(kv.getValue()));
        assertTrue(iterator.hasNext());
        kv = iterator.next();
        assertArrayEquals(expectedRowKey1, kv.getRow());        
        assertNull(PVarchar.INSTANCE.toObject(kv.getValue()));
    }

    @SuppressWarnings("deprecation")
    private static void assertRow2(Iterator<KeyValue> iterator, byte[] expectedRowKey2) {
        KeyValue kv;
        assertTrue(iterator.hasNext());
        kv = iterator.next();
        assertArrayEquals(expectedRowKey2, kv.getRow());        
        assertEquals(name2, PVarchar.INSTANCE.toObject(kv.getValue()));
        assertTrue(iterator.hasNext());
        kv = iterator.next();
        assertArrayEquals(expectedRowKey2, kv.getRow());        
        assertEquals(now, PDate.INSTANCE.toObject(kv.getValue()));
        assertTrue(iterator.hasNext());
        kv = iterator.next();
        assertArrayEquals(expectedRowKey2, kv.getRow());        
        assertNull(PVarchar.INSTANCE.toObject(kv.getValue()));
    }
    
    @Test
    public void testNoConnectionInfo() throws Exception {
        try {
            DriverManager.getConnection(PhoenixRuntime.JDBC_PROTOCOL);
            fail();
        } catch (SQLException e) {
            assertEquals(SQLExceptionCode.MALFORMED_CONNECTION_URL.getSQLState(),e.getSQLState());
        }
    }
    
    @Test
    public void testMultipleConnectionQueryServices() throws Exception {
        String url1 = getUrl();
        String url2 = url1 + PhoenixRuntime.JDBC_PROTOCOL_SEPARATOR + "LongRunningQueries";
        Connection conn1 = DriverManager.getConnection(url1);
        try {
            assertEquals(StringUtil.EMPTY_STRING, conn1.getMetaData().getUserName());
            Connection conn2 = DriverManager.getConnection(url2);
            try {
                assertEquals("LongRunningQueries", conn2.getMetaData().getUserName());
                ConnectionQueryServices cqs1 = conn1.unwrap(PhoenixConnection.class).getQueryServices();
                ConnectionQueryServices cqs2 = conn2.unwrap(PhoenixConnection.class).getQueryServices();
                assertTrue(cqs1 != cqs2);
                Connection conn3 = DriverManager.getConnection(url1);
                try {
                    ConnectionQueryServices cqs3 = conn3.unwrap(PhoenixConnection.class).getQueryServices();
                    assertTrue(cqs1 == cqs3);
                    Connection conn4 = DriverManager.getConnection(url2);
                    try {
                        ConnectionQueryServices cqs4 = conn4.unwrap(PhoenixConnection.class).getQueryServices();
                        assertTrue(cqs2 == cqs4);
                    } finally {
                        conn4.close();
                    }
                } finally {
                    conn3.close();
                }
            } finally {
                conn2.close();
            }
        } finally {
            conn1.close();
        }
        
    }

}