/*
 * ModeShape (http://www.modeshape.org)
 *
 * Licensed 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.modeshape.jcr;

import static org.hamcrest.core.Is.is;
import static org.hamcrest.core.IsNull.notNullValue;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import java.io.File;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.jcr.Binary;
import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.Session;
import javax.jcr.nodetype.NodeType;
import javax.jcr.query.Query;
import javax.jcr.query.QueryResult;
import org.junit.Before;
import org.junit.Test;
import org.modeshape.common.FixFor;
import org.modeshape.common.util.IoUtil;
import org.modeshape.jcr.api.JcrTools;
import org.modeshape.jcr.api.NamespaceRegistry;

/**
 * A test the verifies that a repository will persist content (including binaries).
 */
public class RepositoryPersistenceTest extends MultiPassAbstractTest {

    @Before
    public void before() {
        TestingUtil.waitUntilFolderCleanedUp("target/persistent_repository");            
    }
    
    @Test
    public void shouldPersistBinariesAcrossRestart() throws Exception {
        String repositoryConfigFile = "config/repo-config-binaries-fs.json";
        assertDataPersistenceAcrossRestarts(repositoryConfigFile);
    }

    @FixFor( "MODE-2212" )
    @Test
    public void shouldPersistGeneratedNamespacesAcrossRestart() throws Exception {
        String repositoryConfigFile = "config/repo-config-binaries-fs.json";

        startRunStop(repository -> {
            Session session = repository.login();

            final NamespaceRegistry namespaceRegistry = (NamespaceRegistry)session.getWorkspace().getNamespaceRegistry();

            namespaceRegistry.registerNamespace("info:a#");
            namespaceRegistry.registerNamespace("info:b#");
            namespaceRegistry.registerNamespace("info:c#");
            assertEquals("ns001", namespaceRegistry.getPrefix("info:a#"));
            assertEquals("ns002", namespaceRegistry.getPrefix("info:b#"));
            assertEquals("ns003", namespaceRegistry.getPrefix("info:c#"));

            final Node node = session.getRootNode().addNode("ns001:xyz", NodeType.NT_UNSTRUCTURED);
            node.setProperty("ns002:abc", "abc");
            node.setProperty("ns003:def", "def");

            session.save();
            session.logout();
        }, repositoryConfigFile);

        startRunStop(repository -> {
            Session session = repository.login();

            final NamespaceRegistry namespaceRegistry = (NamespaceRegistry)session.getWorkspace().getNamespaceRegistry();

            assertEquals("ns001", namespaceRegistry.getPrefix("info:a#"));
            assertEquals("ns002", namespaceRegistry.getPrefix("info:b#"));
            assertEquals("ns003", namespaceRegistry.getPrefix("info:c#"));
            session.save();
            session.logout();
        }, repositoryConfigFile);

    }
    
    @Test
    public void shouldPersistDataUsingDB() throws Exception {
        // make sure the DB is clean (empty) when running this test; there is no effective teardown
        assertDataPersistenceAcrossRestarts("config/db/repo-config-jdbc.json");
    }

    private void assertDataPersistenceAcrossRestarts( String repositoryConfigFile ) throws Exception {
        final List<File> testFiles = new ArrayList<>();
        final Map<String, Long> testFileSizesInBytes = new HashMap<>();
        testFiles.add(getFile("mimetype/test.xml"));
        testFiles.add(getFile("mimetype/modeshape.doc"));
        testFiles.add(getFile("mimetype/log4j.properties"));
        for (File testFile : testFiles) {
            assertThat(testFile.getPath() + " should exist", testFile.exists(), is(true));
            assertThat(testFile.getPath() + " should be a file", testFile.isFile(), is(true));
            assertThat(testFile.getPath() + " should be readable", testFile.canRead(), is(true));
            testFileSizesInBytes.put(testFile.getName(), testFile.length());
        }

        final JcrTools tools = new JcrTools();

        startRunStop(repository -> {
            Session session = repository.login();

            // Add some content ...
            session.getRootNode().addNode("testNode");
            for (File testFile : testFiles) {
                String name = testFile.getName();
                Node fileNode = tools.uploadFile(session, "/testNode/" + name, testFile);
                Binary binary = fileNode.getNode("jcr:content").getProperty("jcr:data").getBinary();
                assertThat(binary.getSize(), is(testFileSizesInBytes.get(name)));
            }

            session.save();

            Node testNode = session.getNode("/testNode");
            for (File testFile : testFiles) {
                String name = testFile.getName();
                Node fileNode = testNode.getNode(name);
                assertThat(fileNode, is(notNullValue()));
                Binary binary = fileNode.getNode("jcr:content").getProperty("jcr:data").getBinary();
                byte[] expectedBytes = IoUtil.readBytes(testFile);
                byte[] actualBytes = IoUtil.readBytes(binary.getStream());
                assertArrayEquals(expectedBytes, actualBytes);
            }

            Query query = session.getWorkspace().getQueryManager().createQuery("SELECT * FROM [nt:file]", Query.JCR_SQL2);
            QueryResult results = query.execute();
            NodeIterator iter = results.getNodes();
            while (iter.hasNext()) {
                Node fileNode = iter.nextNode();
                assertThat(fileNode, is(notNullValue()));
                String name = fileNode.getName();
                Binary binary = fileNode.getNode("jcr:content").getProperty("jcr:data").getBinary();
                assertThat(binary.getSize(), is(testFileSizesInBytes.get(name)));
            }

            session.logout();
        }, repositoryConfigFile);

        startRunStop(repository -> {

            Session session = repository.login();
            assertNotNull(session.getNode("/testNode"));

            for (File testFile : testFiles) {
                String name = testFile.getName();
                Node fileNode = session.getNode("/testNode/" + name);
                assertNotNull(fileNode);
                Binary binary = fileNode.getNode("jcr:content").getProperty("jcr:data").getBinary();
                assertThat(binary.getSize(), is(testFileSizesInBytes.get(name)));
            }

            Query query = session.getWorkspace().getQueryManager().createQuery("SELECT * FROM [nt:file]", Query.JCR_SQL2);
            QueryResult results = query.execute();
            NodeIterator iter = results.getNodes();
            while (iter.hasNext()) {
                Node fileNode = iter.nextNode();
                String name = fileNode.getName();
                Binary binary = fileNode.getNode("jcr:content").getProperty("jcr:data").getBinary();
                assertThat(binary.getSize(), is(testFileSizesInBytes.get(name)));
            }

            
            session.getNode("/testNode").remove();
            session.save();

            query = session.getWorkspace().getQueryManager().createQuery("SELECT * FROM [nt:file]", Query.JCR_SQL2);
            results = query.execute();
            assertFalse(results.getNodes().hasNext());
            
            session.logout();
        }, repositoryConfigFile);
    }

    protected File getFile( String resourcePath ) throws URISyntaxException {
        URL resource = getClass().getClassLoader().getResource(resourcePath);
        assertNotNull(resourcePath + " not found", resource);
        return new File(resource.toURI());
    }
}
