/*
 * ModeShape (http://www.modeshape.org)
 * See the COPYRIGHT.txt file distributed with this work for information
 * regarding copyright ownership.  Some portions may be licensed
 * to Red Hat, Inc. under one or more contributor license agreements.
 * See the AUTHORS.txt file in the distribution for a full listing of
 * individual contributors.
 *
 * ModeShape is free software. Unless otherwise indicated, all code in ModeShape
 * is licensed to you under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * ModeShape is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

package org.modeshape.web.jcr.webdav;

import static org.hamcrest.core.Is.is;
import static org.hamcrest.core.IsNull.notNullValue;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.when;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.security.Principal;
import java.util.Calendar;
import java.util.LinkedList;
import java.util.List;
import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.http.HttpServletRequest;
import net.sf.webdav.IWebdavStore;
import net.sf.webdav.StoredObject;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.modeshape.common.util.IoUtil;
import org.modeshape.jcr.JcrRepository;
import org.modeshape.jcr.TestingUtil;
import org.modeshape.jcr.api.RepositoryFactory;
import org.modeshape.web.jcr.ModeShapeJcrDeployer;
import org.modeshape.web.jcr.RepositoryManager;
import org.modeshape.web.jcr.webdav.ModeShapeWebdavStore.JcrSessionTransaction;

public class ModeShapeWebdavStoreTest {

    private static final String[] EMPTY_STRING_ARRAY = new String[0];
    private static final String REPOSITORY_NAME = "repo";
    private static final String WORKSPACE_NAME = "default";

    private IWebdavStore store;
    private JcrSessionTransaction tx;
    private ServletContextListener contextListener = new ModeShapeJcrDeployer();
    private Session session;
    private JcrRepository repository;

    @Mock
    private Principal principal;
    @Mock
    private HttpServletRequest request;
    @Mock
    private ServletContext context;
    @Mock
    private ServletContextEvent event;

    @Before
    public void beforeEach() throws Exception {
        MockitoAnnotations.initMocks(this);

        when(principal.getName()).thenReturn("testuser");
        when(request.getUserPrincipal()).thenReturn(principal);
        when(request.isUserInRole("readwrite")).thenReturn(true);
        when(context.getInitParameter(RepositoryFactory.URL)).thenReturn("file:///repository-config.json");
        when(context.getInitParameter(SingleRepositoryRequestResolver.INIT_REPOSITORY_NAME)).thenReturn(REPOSITORY_NAME);
        when(context.getInitParameter(SingleRepositoryRequestResolver.INIT_WORKSPACE_NAME)).thenReturn(WORKSPACE_NAME);
        when(event.getServletContext()).thenReturn(context);

        RequestResolver uriResolver = new SingleRepositoryRequestResolver();
        uriResolver.initialize(context);

        ContentMapper contentMapper = new DefaultContentMapper();
        contentMapper.initialize(context);
        store = new ModeShapeWebdavStore(uriResolver, contentMapper);

        contextListener.contextInitialized(event);

        ModeShapeWebdavStore.setRequest(request);

        session = RepositoryManager.getSession(request, REPOSITORY_NAME, WORKSPACE_NAME);
        repository = (JcrRepository)session.getRepository();

        tx = (JcrSessionTransaction)store.begin(principal);

        assertThat(tx.owns(session), is(false));
    }

    @After
    public void afterEach() throws Exception {
        ModeShapeWebdavStore.setRequest(null);
        contextListener.contextDestroyed(event);
        TestingUtil.killRepository(repository);
    }

    @Test
    public void shouldBeginTransaction() throws Exception {
        assertThat(tx, is(notNullValue()));
    }

    @Test
    public void shouldCommitTransaction() throws Exception {
        store.commit(tx);
    }

    @Test( expected = IllegalArgumentException.class )
    public void shouldNotCommitNullTransaction() throws Exception {
        store.commit(null);
    }

    private void compareChildrenWithSession( String[] children,
                                             String path ) throws Exception {
        List<String> sessChildren = new LinkedList<String>();
        Node node = (Node)session.getItem(path);
        for (NodeIterator iter = node.getNodes(); iter.hasNext();) {
            sessChildren.add(iter.nextNode().getName());
        }

        assertThat(children, is(sessChildren.toArray(EMPTY_STRING_ARRAY)));

    }

    @Test
    public void shouldListFilesAtRoot() throws Exception {
        String[] children = store.getChildrenNames(tx, "/");
        compareChildrenWithSession(children, "/");
    }

    @Test
    public void shouldAddFolderAtRoot() throws Exception {
        String[] children = store.getChildrenNames(tx, "/");

        store.createFolder(tx, "/newFolder");

        // Make sure that the node isn't visible yet
        compareChildrenWithSession(children, "/");

        store.commit(tx);

        session.refresh(false);

        // Make sure that the node is visible after the commit
        compareChildrenWithSession(store.getChildrenNames(tx, "/"), "/");
    }

    @Test
    public void shouldAddFileAtRoot() throws Exception {
        String[] children = store.getChildrenNames(tx, "/");

        store.createResource(tx, "/newFile");

        // Make sure that the node isn't visible yet
        compareChildrenWithSession(children, "/");

        store.commit(tx);

        session.refresh(false);

        // Make sure that the node is visible after the commit
        compareChildrenWithSession(store.getChildrenNames(tx, "/"), "/");

    }

    @Test
    public void shouldReadFileLengthAndContent() throws Exception {
        final String TEST_STRING = "This is my miraculous test string!";

        Node fileNode = rootNode().addNode("newFile", "nt:file");
        Node contentNode = fileNode.addNode("jcr:content", "mode:resource");
        contentNode.setProperty("jcr:data", TEST_STRING);
        contentNode.setProperty("jcr:mimeType", "text/plain");
        contentNode.setProperty("jcr:encoding", "UTF-8");
        contentNode.setProperty("jcr:lastModified", Calendar.getInstance());
        session.save();

        when(request.getPathInfo()).thenReturn("/newFile");
        assertThat((int)store.getResourceLength(tx, "/newFile"), is(TEST_STRING.length()));

        InputStream is = store.getResourceContent(tx, "/newFile");
        String webdavContent = IoUtil.read(is);
        assertThat(webdavContent, is(TEST_STRING));
    }

    @Test
    public void shouldRemoveFile() throws Exception {
        final String TEST_STRING = "This is my miraculous test string!";

        Node fileNode = rootNode().addNode("newFile", "nt:file");
        Node contentNode = fileNode.addNode("jcr:content", "mode:resource");
        contentNode.setProperty("jcr:data", TEST_STRING);
        contentNode.setProperty("jcr:mimeType", "text/plain");
        contentNode.setProperty("jcr:encoding", "UTF-8");
        contentNode.setProperty("jcr:lastModified", Calendar.getInstance());
        session.save();

        when(request.getPathInfo()).thenReturn("/newFile");
        store.removeObject(tx, "/newFile");
        store.commit(tx);

        session.refresh(false);
        assertThat(rootNode().hasNode("newFile"), is(false));
    }

    private Node rootNode() throws RepositoryException {
        return session.getRootNode();
    }

    @Test
    public void shouldSimulateAddingFileThroughWebdav() throws Exception {
        final String TEST_STRING = "This is my miraculous test string!";
        final String TEST_URI = "/TestFile.rtf";

        when(request.getPathInfo()).thenReturn("");
        store.getStoredObject(tx, "");

        when(request.getPathInfo()).thenReturn(TEST_URI);
        store.getStoredObject(tx, TEST_URI);

        when(request.getPathInfo()).thenReturn("/"); // will ask for parent during resolution ...
        store.createResource(tx, TEST_URI);

        when(request.getPathInfo()).thenReturn(TEST_URI);
        long length = store.setResourceContent(tx,
                                               TEST_URI,
                                               new ByteArrayInputStream(TEST_STRING.getBytes()),
                                               "text/plain",
                                               "UTF-8");

        assertThat((int)length, is(TEST_STRING.getBytes().length));

        StoredObject ob = store.getStoredObject(tx, TEST_URI);

        assertThat(ob, is(notNullValue()));
    }

}
