import java.io.File;
import java.nio.file.Paths;
import java.security.SecureRandom;
import java.time.Instant;
import java.util.Map;
import java.util.concurrent.CountDownLatch;

import com.arcadedb.ContextConfiguration;
import com.arcadedb.GlobalConfiguration;
import com.arcadedb.engine.ComponentFile.MODE;
import com.arcadedb.remote.RemoteDatabase;
import com.arcadedb.server.ArcadeDBServer;

public class ArcadeDBTest {
	public static void main(String[] args) {
		ContextConfiguration arcadeConfig = new ContextConfiguration();
		arcadeConfig.setValue(GlobalConfiguration.COMMIT_LOCK_TIMEOUT, 600000L);
		arcadeConfig.setValue(GlobalConfiguration.SERVER_HTTP_SESSION_EXPIRE_TIMEOUT, 600000L);
		GlobalConfiguration.SERVER_HTTP_SESSION_EXPIRE_TIMEOUT.setValue(600000L);
		GlobalConfiguration.COMMIT_LOCK_TIMEOUT.setValue(600000L);
		GlobalConfiguration.EXPLICIT_LOCK_TIMEOUT.setValue(600000L);
		GlobalConfiguration.NETWORK_SOCKET_TIMEOUT.setValue(600000L);

		ArcadeDBServer server = new ArcadeDBServer(arcadeConfig);
		server.start();
		server.createDatabase("lockDb", MODE.READ_WRITE);
		try (RemoteDatabase database = getRemoteDB()) {
			for (int i = 0; i < 30; i++) {
				database.command("sql", "create vertex type Dummy" + i + " if not exists;");
				database.command("sql", "create property Dummy" + i + ".name if not exists string;");
				database.command("sql", "create property Dummy" + i + ".value if not exists string;");
				database.command("sql", "alter type Dummy" + i + " BucketSelectionStrategy `thread`");
				database.command("sql", "create index if not exists on Dummy" + i + " (name,value) unique");
				database.command("sql", "create index if not exists on Dummy" + i + " (name) notunique");
			}
			database.command("sql", "rebuild index *");
		} catch (Exception e) {
			System.out.println(e.getMessage());
		}
		threadWait(1000);
		try {
			CountDownLatch latch = new CountDownLatch(500);
			for (int i = 0; i < 500; i++) {
				new Thread(() -> {
					long threadId = Thread.currentThread().getId();
					// System.out.println(Instant.now() + ":started thread " + threadId);

					try (RemoteDatabase database = getRemoteDB()) {
						getLock(database);

						insertData(database, String.valueOf(threadId));
					} catch (Exception e) {
						System.out
								.println(Instant.now() + ": " + Thread.currentThread().getId() + ": " + e.getMessage());
					}
					latch.countDown();
				}).start();
			}
			latch.await();
			System.out.println(Instant.now() + ": done with operations");
			// keep thread running for analysis
			threadWait(100000000);
		} catch (Exception e) {
			System.out.println(e.getMessage());
		} finally {
			server.stop();
		}
	}

	private static void threadWait(long timeMs) {
		try {
			Thread.sleep(timeMs);
		} catch (InterruptedException e) {
			System.out.println(e.getMessage());
		}
	}

	private static RemoteDatabase getRemoteDB() {
		return new RemoteDatabase("localhost", 2480, "lockDb", "root", "playwithdata",
				new ContextConfiguration(Map.of(GlobalConfiguration.NETWORK_SOCKET_TIMEOUT.getKey(), 600000L,
						GlobalConfiguration.TX_RETRIES.getKey(), 15, GlobalConfiguration.TX_RETRY_DELAY.getKey(), 50,
						GlobalConfiguration.COMMIT_LOCK_TIMEOUT.getKey(), 600000L,
						GlobalConfiguration.SERVER_HTTP_SESSION_EXPIRE_TIMEOUT.getKey(), 600000L)));
	}

	private static void insertData(RemoteDatabase database, String id) {
		for (int j = 0; j < 1; j++) {
			final int type = new SecureRandom().nextInt(30);

			database.command("sql", "insert into Dummy" + type + "(name,value) values ('"
					+ Thread.currentThread().getId() + "','" + id + "|" + j + "');");
		}

		database.commit();
		long threadId = Thread.currentThread().getId();
		// System.out.println(Instant.now() + ":added data for " + threadId);

	}

	private static void getLock(RemoteDatabase database) {
		database.begin();
		var lock = database.acquireLock();
		for (int j = 0; j < 30; j++) {
			lock.type("Dummy" + j);
		}
		long threadId = Thread.currentThread().getId();
		// System.out.println(Instant.now() + ":trying to get lock for thread " +
		// threadId);
		lock.lock();
		// System.out.println(Instant.now() + ":got lock for thread " + threadId);
	}
}
