/*
 * 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.vault.fs.io;

import java.io.IOException;
import java.io.InputStream;
import java.util.Calendar;

import javax.jcr.Binary;
import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.RepositoryException;

import org.apache.jackrabbit.vault.fs.api.Artifact;
import org.apache.jackrabbit.vault.fs.api.VaultFile;
import org.apache.jackrabbit.vault.util.JcrConstants;
import org.apache.jackrabbit.vault.util.PathUtil;
import org.apache.jackrabbit.vault.util.PlatformNameFormat;
import org.apache.jackrabbit.vault.util.Text;

/**
 * Implements a Vault filesystem exporter that exports Vault files to a JCR
 * repository.
 * It uses the {@link PlatformNameFormat} for formatting the jcr file
 * names to local ones.
 */
public class JcrExporter extends AbstractExporter {

    private final Node localParent;

    private boolean autoDeleteFiles;

    /**
     * Constructs a new jcr exporter.
     * @param localFile the local parent folder
     */
    public JcrExporter(Node localFile) {
        this.localParent = localFile;
    }

    public boolean isAutoDeleteFiles() {
        return autoDeleteFiles;
    }

    public void setAutoDeleteFiles(boolean autoDeleteFiles) {
        this.autoDeleteFiles = autoDeleteFiles;
    }

    /**
     * {@inheritDoc}
     */
    public void open() throws IOException, RepositoryException {
        scan(localParent);
    }

    /**
     * {@inheritDoc}
     */
    public void close() throws IOException, RepositoryException {
        if (autoDeleteFiles) {
            for (ExportInfo.Entry e: exportInfo.getEntries().values()) {
                if (e.type == ExportInfo.Type.DELETE) {
                    String relPath = PathUtil.getRelativePath(localParent.getPath(), e.path);
                    try {
                        Node node = localParent.getNode(relPath);
                        node.remove();
                        track("D", relPath);
                    } catch (RepositoryException e1) {
                        track(e1, relPath);
                    }
                }
            }
        }
        localParent.getSession().save();
    }

    private void scan(Node dir) throws RepositoryException {
        NodeIterator iter = dir.getNodes();
        while (iter.hasNext()) {
            Node child = iter.nextNode();
            String name = child.getName();
            if (".svn".equals(name) || ".vlt".equals(name)) {
                continue;
            }
            if (child.isNodeType(JcrConstants.NT_FOLDER)) {
                exportInfo.update(ExportInfo.Type.RMDIR, child.getPath());
                scan(child);
            } else if (child.isNodeType(JcrConstants.NT_FILE)) {
                exportInfo.update(ExportInfo.Type.DELETE, child.getPath());
            }
        }
    }

    public void createDirectory(VaultFile file, String relPath)
            throws RepositoryException, IOException {
        getOrCreateItem(getPlatformFilePath(file, relPath), true);
    }

    public void createDirectory(String relPath) throws IOException {
        getOrCreateItem(relPath, true);
    }

    public void writeFile(VaultFile file, String relPath)
            throws RepositoryException, IOException {
        Node local = getOrCreateItem(getPlatformFilePath(file, relPath), false);
        track(local.isNew() ? "A" : "U", relPath);
        Node content;
        if (local.hasNode(JcrConstants.JCR_CONTENT)) {
            content = local.getNode(JcrConstants.JCR_CONTENT);
        } else {
            content = local.addNode(JcrConstants.JCR_CONTENT, JcrConstants.NT_RESOURCE);
        }
        Artifact a = file.getArtifact();
        switch (a.getPreferredAccess()) {
            case NONE:
                throw new RepositoryException("Artifact has no content.");

            case SPOOL:
                // we can't support spool
            case STREAM:
                try (InputStream in = a.getInputStream()) {
                    Binary b = content.getSession().getValueFactory().createBinary(in);
                    content.setProperty(JcrConstants.JCR_DATA, b);
                    b.dispose();
                }
                break;
        }
        Calendar now = Calendar.getInstance();
        if (a.getLastModified() >= 0) {
            now.setTimeInMillis(a.getLastModified());
        }
        content.setProperty(JcrConstants.JCR_LASTMODIFIED, now);
        if (a.getContentType() != null) {
            content.setProperty(JcrConstants.JCR_MIMETYPE, a.getContentType());
        } else if (!content.hasProperty(JcrConstants.JCR_MIMETYPE)){
            content.setProperty(JcrConstants.JCR_MIMETYPE, "application/octet-stream");
        }
    }

    public void writeFile(InputStream in, String relPath) throws IOException {
        try {
            Node content;
            Node local = getOrCreateItem(relPath, false);
            if (local.hasNode(JcrConstants.JCR_CONTENT)) {
                content = local.getNode(JcrConstants.JCR_CONTENT);
            } else {
                content = local.addNode(JcrConstants.JCR_CONTENT, JcrConstants.NT_RESOURCE);
            }
            Binary b = content.getSession().getValueFactory().createBinary(in);
            content.setProperty(JcrConstants.JCR_DATA, b);
            content.setProperty(JcrConstants.JCR_LASTMODIFIED, Calendar.getInstance());
            if (!content.hasProperty(JcrConstants.JCR_MIMETYPE)){
                content.setProperty(JcrConstants.JCR_MIMETYPE, "application/octet-stream");
            }
            b.dispose();
            in.close();
        } catch (RepositoryException e) {
            IOException io = new IOException("Error while writing file " + relPath);
            io.initCause(e);
            throw io;
        }
    }

    private Node getOrCreateItem(String relPath, boolean isDir) throws IOException {
        try {
            String[] segments = Text.explode(relPath, '/');
            Node root = localParent;
            for (int i=0; i<segments.length; i++) {
                String s = segments[i];
                if (root.hasNode(s)) {
                    root = root.getNode(s);
                    if (isDir) {
                        exportInfo.update(ExportInfo.Type.NOP, root.getPath());
                    } else {
                        exportInfo.update(ExportInfo.Type.UPDATE, root.getPath());
                    }
                } else {
                    if (i == segments.length -1 && !isDir) {
                        root = root.addNode(s, JcrConstants.NT_FILE);
                        exportInfo.update(ExportInfo.Type.ADD, root.getPath());
                    } else {
                        root = root.addNode(s, JcrConstants.NT_FOLDER);
                        exportInfo.update(ExportInfo.Type.MKDIR, root.getPath());
                    }
                }
            }
            return root;
        } catch (RepositoryException e) {
            IOException io = new IOException("Error while creating item " + relPath);
            io.initCause(e);
            throw io;
        }
    }

}