/*
 * 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.core.cluster;

import java.io.File;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.util.UUID;

import javax.jcr.Node;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.SimpleCredentials;
import javax.jcr.query.Query;
import javax.jcr.query.RowIterator;

import junit.framework.Assert;

import org.apache.commons.io.FileUtils;
import org.apache.jackrabbit.JcrConstants;
import org.apache.jackrabbit.commons.JcrUtils;
import org.apache.jackrabbit.core.RepositoryImpl;
import org.apache.jackrabbit.core.config.RepositoryConfig;
import org.apache.jackrabbit.test.JUnitTest;
import org.h2.tools.Server;

/**
 * Test for JCR3162
 */
public class DbClusterTestJCR3162 extends JUnitTest {

    private static final SimpleCredentials ADMIN = new SimpleCredentials(
            "admin", "admin".toCharArray());

    private Server server1;
    private Server server2;

    private RepositoryImpl rep1;
    private RepositoryImpl rep2;

    private String clusterId1 = UUID.randomUUID().toString();
    private String clusterId2 = UUID.randomUUID().toString();

    private String prevClusterId;

    public void setUp() throws Exception {
        deleteAll();
        server1 = Server.createTcpServer("-tcpPort", "9001", "-baseDir",
                "./target/dbClusterTest/db1", "-tcpAllowOthers").start();
        server2 = Server.createTcpServer("-tcpPort", "9002", "-baseDir",
                "./target/dbClusterTest/db2", "-tcpAllowOthers").start();
        FileUtils
                .copyFile(
                        new File(
                                "./src/test/resources/org/apache/jackrabbit/core/cluster/repository-h2.xml"),
                        new File("./target/dbClusterTest/node1/repository.xml"));
        FileUtils
                .copyFile(
                        new File(
                                "./src/test/resources/org/apache/jackrabbit/core/cluster/repository-h2.xml"),
                        new File("./target/dbClusterTest/node2/repository.xml"));

        prevClusterId = System.setProperty(ClusterNode.SYSTEM_PROPERTY_NODE_ID, clusterId1);
        rep1 = RepositoryImpl.create(RepositoryConfig.create(new File(
                "./target/dbClusterTest/node1")));

        System.setProperty(ClusterNode.SYSTEM_PROPERTY_NODE_ID, clusterId2);

    }

    public void tearDown() throws Exception {
        // revert change to system property
        if (prevClusterId == null) {
            System.clearProperty(ClusterNode.SYSTEM_PROPERTY_NODE_ID);
        }
        else {
            System.setProperty(ClusterNode.SYSTEM_PROPERTY_NODE_ID, prevClusterId);
        }

        try {
            rep1.shutdown();
            if (rep2 != null) {
                rep2.shutdown();
            }
        } finally {
            server1.stop();
            server2.stop();
            deleteAll();
        }
    }

    private void deleteAll() throws IOException {
        FileUtils.deleteDirectory(new File("./target/dbClusterTest"));
    }

    public void test() throws RepositoryException {
        int count = 5;

        // 1. create
        Session s1 = rep1.login(ADMIN);
        Node n = s1.getRootNode().addNode(
                "test-cluster-" + System.currentTimeMillis(),
                JcrConstants.NT_UNSTRUCTURED);
        n.addMixin(JcrConstants.MIX_VERSIONABLE);
        for (int i = 0; i < count; i++) {
            Node c = n.addNode("child_" + i);
            c.addMixin(JcrConstants.MIX_VERSIONABLE);
        }
        s1.save();

        // 2. rollback journal revision
        resetJournalRev();

        // 3. sync & verify
        // rep1.shutdown();

        // start #2 with an empty search index
        rep2 = RepositoryImpl.create(RepositoryConfig.create(new File(
                "./target/dbClusterTest/node2")));

        // verify
        Session s2 = rep2.login(ADMIN);
        checkConsistency(s2, "/", s2.getRootNode().getNodes().getSize());
    }

    private void resetJournalRev() {
        Connection con = null;
        try {
            con = DriverManager.getConnection(
                    "jdbc:h2:tcp://localhost:9001,localhost:9002/db", "sa",
                    "sa");
            PreparedStatement prep = con
                    .prepareStatement("update JOURNAL_LOCAL_REVISIONS set REVISION_ID=0 where JOURNAL_ID=?");
            prep.setString(1, clusterId2);
            prep.executeUpdate();
            prep.close();
        } catch (Exception e) {
            e.printStackTrace();
            Assert.fail("Unable to reset revision to 0. " + e.getMessage());
        } finally {
            if (con != null) {
                try {
                    con.close();
                } catch (Exception e) {
                    // e.printStackTrace();
                }
            }
        }
    }

    private void checkConsistency(Session s, String path, long nodes)
            throws RepositoryException {

        s.refresh(true);
        Node n = s.getNode(path);

        RowIterator result = s
                .getWorkspace()
                .getQueryManager()
                .createQuery(
                        "SELECT * FROM [" + JcrConstants.NT_BASE
                                + "] as NODE WHERE ischildnode(NODE, ['"
                                + n.getPath() + "'])", Query.JCR_SQL2)
                .execute().getRows();

        int foundViaQuery = 0;
        while (result.hasNext()) {
            result.next();
            foundViaQuery++;
        }

        StringBuilder err = new StringBuilder("Path " + n.getPath() + ": ");
        for (Node c : JcrUtils.getChildNodes(n)) {
            err.append("(");
            err.append(c.getPath());
            err.append("|");
            err.append(c.getPrimaryNodeType().getName());
            err.append("),");
        }
        Assert.assertEquals(err.toString(), nodes, foundViaQuery);

        for (Node c : JcrUtils.getChildNodes(n)) {
            checkConsistency(s, c.getPath(), c.getNodes().getSize());
        }
    }
}
