/*
 * 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.jackrabbit.oak.benchmark;

import javax.jcr.Node;
import javax.jcr.Repository;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.SimpleCredentials;

import org.apache.jackrabbit.oak.fixture.RepositoryFixture;

public class ReplicaCrashResilienceTest extends Benchmark {

    private static final String LEVEL2POINTER = "level2pointer";
    private static final String LEVEL1POINTER = "level1pointer";
    private static final String WRITER_INFOS = "writerInfos";
    private static final String REPLICA_CRASH_TEST = "replicaCrashTest-"+System.currentTimeMillis();

    @Override
    public void run(Iterable<RepositoryFixture> fixtures) {
        for (RepositoryFixture fixture : fixtures) {
            if (fixture.isAvailable(1)) {
                System.out.format("%s: ReplicaCrashResilienceTest%n", fixture);
                try {
                    Repository[] cluster = fixture.setUpCluster(1);
                    try {
                        run(cluster[0]);
                    } finally {
                        fixture.tearDownCluster();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    System.exit(1);
                }
            }
        }
    }

    private void run(final Repository repository) throws Exception {
        System.out.println("Setup...");
        try {
            Session session = repository.login(
                    new SimpleCredentials("admin", "admin".toCharArray()));
            final Node rootNode = session.getRootNode();
            if (rootNode.hasNode(REPLICA_CRASH_TEST)) {
                // then cleanup first
                rootNode.getNode(REPLICA_CRASH_TEST).remove();
                session.save();
            }
            session.refresh(false);
            final Node replicaCrashTestNode = rootNode.addNode(REPLICA_CRASH_TEST);
            replicaCrashTestNode.addNode(WRITER_INFOS);
            session.save();
        } catch (RepositoryException e1) {
            e1.printStackTrace();
            System.exit(1);
        }
        // this runnable will go ahead and start writing nodes to the repo
        // the structure is:
        // /replicaCrashTest/writerInfos
        //                           - level 1 pointer
        //                           - level 2 pointer
        // /replicaCrashTest/[1-n]/[1-1000]
        Runnable writer = new Runnable() {

            @Override
            public void run() {
                int level1Pointer = 1;
                int level2Pointer = 1;
                Session session = null;
                try {
                    session = repository.login(
                            new SimpleCredentials("admin", "admin".toCharArray()));
                } catch (Exception e1) {
                    e1.printStackTrace();
                    System.exit(1);
                }
                System.out.println("Writer: Test start.");
                while(true) {
                    try{
                        final String level1 = String.valueOf(level1Pointer);
                        final String level2 = String.valueOf(level2Pointer);

                        final Node rootNode = session.getRootNode();
                        final Node replicaCrashTestNode = rootNode.getNode(REPLICA_CRASH_TEST);
                        final Node writerInfosNode = replicaCrashTestNode.getNode(WRITER_INFOS);

                        Node level1Node;
                        if (replicaCrashTestNode.hasNode(level1)) {
                            level1Node = replicaCrashTestNode.getNode(level1);
                        } else {
                            level1Node = replicaCrashTestNode.addNode(level1);
                            System.out.println("Writer: Created level1 node: "+level1Node);
                        }
                        Node level2Node = level1Node.addNode(level2);
                        System.out.println("Writer: Created level2 node: "+level2Node);
                        writerInfosNode.setProperty(LEVEL1POINTER, level1Pointer);
                        writerInfosNode.setProperty(LEVEL2POINTER, level2Pointer);
                        session.save();
                    } catch (org.apache.jackrabbit.guava.common.util.concurrent.UncheckedExecutionException e) {
                        System.out.println("Got an UncheckedException (levels: " + level1Pointer + "/" + level2Pointer
                                + ") from the google cache probably: " + e);
                        try {
                            Thread.sleep(500);
                        } catch (InterruptedException e2) {
                            e.printStackTrace();
                            System.exit(1);
                        }
                        continue;
                    } catch (Throwable e) { // yes, one should not catch Throwable - but this is a test only
                        e.printStackTrace(System.out);
                        System.exit(1);
                    }
                    if (++level2Pointer>1000) {
                        level2Pointer = 1;
                        level1Pointer++;
                    }
                }
            }

        };
        Thread th1 = new Thread(writer);
        System.out.println("Launching writer...");
        th1.start();

        Runnable reader = new Runnable() {

            @Override
            public void run() {
                long level1Pointer = 1;
                long level2Pointer = 1;
                Session session = null;
                try {
                    session = repository.login(
                            new SimpleCredentials("admin", "admin".toCharArray()));

                    Node rootNode = session.getRootNode();
                    Node replicaCrashTestNode = rootNode.getNode(REPLICA_CRASH_TEST);
                    Node writerInfos;

                    while(true) {
                        try{
                            final String level1 = String.valueOf(level1Pointer);
                            final String level2 = String.valueOf(level2Pointer);
                            session.refresh(false);
                            writerInfos = replicaCrashTestNode.getNode(WRITER_INFOS);
                            long writerLevel1Pointer = writerInfos.getProperty(LEVEL1POINTER).getLong();
                            long writerLevel2Pointer = writerInfos.getProperty(LEVEL2POINTER).getLong();

                            long writerPointer = writerLevel1Pointer * 1000 + writerLevel2Pointer;
                            long myPointer = level1Pointer * 1000 + level2Pointer;
                            long diff = writerPointer - myPointer;
                            if (diff<100) {
                                System.out.println("Reader: Closer than 100, waiting...level1="+level1+", level2="+level2);
                                try {
                                    Thread.sleep(1000);
                                } catch (InterruptedException e) {
                                    e.printStackTrace();
                                    System.exit(1);
                                }
                                continue;
                            }


                            rootNode = session.getRootNode();
                            replicaCrashTestNode = rootNode.getNode(REPLICA_CRASH_TEST);

                            Node level1Node = replicaCrashTestNode.getNode(level1);
                            if (!level1Node.hasNode(level2)) {
                                System.err.println("Reader: NOT FOUND: level1="+level1+", level2="+level2);
                                Thread.sleep(500);
                                session.refresh(false);
                                System.err.println("Reader: Reverifying once...");
                                rootNode = session.getRootNode();
                                replicaCrashTestNode = rootNode.getNode(REPLICA_CRASH_TEST);
                                level1Node = replicaCrashTestNode.getNode(level1);
                                final boolean hasNode = level1Node.hasNode(level2);
                                if (hasNode) {
                                    System.err.println("Reader: yup, exists: "+hasNode+", level1="+level1+", level2="+level2);
                                } else {
                                    System.err.println("Reader: not found: level1="+level1+", level2="+level2);

                                }
                            } else {
                                // read it
                                Node level2Node = level1Node.getNode(level2);
                                System.out.println("Reader: verified level1="+level1+", level2="+level2);
                            }

                        } catch (org.apache.jackrabbit.guava.common.util.concurrent.UncheckedExecutionException e) {
                            System.out.println("Got an UncheckedException from the google cache probably: " + e);
                            try {
                                Thread.sleep(500);
                            } catch (InterruptedException e2) {
                                e.printStackTrace();
                                System.exit(1);
                            }
                            continue;
                        } catch (Throwable e) { // yes, one should not catch Throwable - but this is a test only
                            e.printStackTrace(System.out);
                            System.exit(1);
                        }
                        if (++level2Pointer>1000) {
                            level2Pointer = 1;
                            level1Pointer++;
                        }
                    }

                } catch (RepositoryException e1) {
                    e1.printStackTrace(System.out);
                    System.exit(1);
                }
                System.out.println("Test start.");

            }
        };
        Thread th2 = new Thread(reader);
        Thread.sleep(1000);
        th2.start();

        System.out.println("Waiting for writer to finish...");
        th1.join();
    }
}