/**
 * Copyright 2015 DuraSpace, Inc.
 *
 * 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.fcrepo.sword.service;

import org.apache.abdera.Abdera;
import org.apache.abdera.model.Service;
import org.fcrepo.http.commons.session.SessionFactory;
import org.fcrepo.kernel.api.RdfLexicon;
import org.fcrepo.kernel.api.models.Container;
import org.fcrepo.kernel.api.models.FedoraResource;
import org.fcrepo.kernel.api.services.ContainerService;
import org.fcrepo.kernel.api.services.NodeService;
import org.fcrepo.kernel.api.utils.NamespaceTools;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.jcr.NamespaceRegistry;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.Value;
import java.util.Map;

import static com.hp.hpl.jena.rdf.model.ResourceFactory.createProperty;
import static java.util.Collections.emptyMap;
import static org.fcrepo.kernel.modeshape.rdf.converters.PropertyConverter.getPropertyNameFromPredicate;

/**
 * Implements all protocol related methods to be executed via {@link org.fcrepo.sword.http.SWORDWebResource}
 *
 * @author claussni
 */
@Component
public class SWORDProviderService {

    public static final String SWORD_ROOT_PATH        = "/sword";
    public static final String SWORD_COLLECTIONS_PATH = SWORD_ROOT_PATH + "/collections";
    public static final String SWORD_WORKSPACES_PATH  = SWORD_ROOT_PATH + "/workspaces";

    private static final String  NS_SWORD                 = "http://purl.org/net/sword/";
    private static final String  NS_SWORD_TERMS           = "http://purl.org/net/sword/terms/";
    private static final String  RDF_PREFIX               = "sword";
    private static final String  SWORD_ROOT_LABEL         = "SWORD root";
    private static final String  SWORD_VERSION            = "2.0";
    private static final Boolean SWORD_SUPPORTS_VERBOSE   = true;
    private static final Boolean SWORD_SUPPORTS_NOOP      = true;
    private static final Integer SWORD_MAX_UPLOAD_SIZE_KB = Integer.MAX_VALUE;

    private static final Logger log    = LoggerFactory.getLogger(SWORDProviderService.class);
    private final        Abdera abdera = new Abdera();

    @Autowired
    private SessionFactory sessionFactory;

    @Autowired
    private NodeService nodeService;

    @Autowired
    private ContainerService containerService;

    private Container workspaces;

    /**
     * Build and return a SWORD service document.
     *
     * @return SWORD service document
     */
    public Service serviceDocument() {
        final Service service = abdera.newService();
        service.addSimpleExtension(NS_SWORD_TERMS, "version", "sword", SWORD_VERSION);
        service.addSimpleExtension(NS_SWORD_TERMS, "maxUploadSize", "sword", String.valueOf(SWORD_MAX_UPLOAD_SIZE_KB));
        workspaces.getChildren().forEachRemaining(fedoraResource -> addWorkspaces(service, fedoraResource));
        return service;
    }

    private void addWorkspaces(final Service service, final FedoraResource fedoraResource) {
        try {
            final Value[] dcTitles = fedoraResource.getProperty("dc:title").getValues();
            if (dcTitles.length > 0) {
                try {
                    final String title = dcTitles[0].getString();
                    if (!title.isEmpty()) {
                        service.addWorkspace(title);
                    }
                } catch (IndexOutOfBoundsException | NullPointerException e) {
                    log.warn("Found workspace container without dc:title property: {}", fedoraResource.getPath());
                }
            }
        } catch (RepositoryException e) {
            log.warn("Found workspace container with invalid dc:title property: {}", fedoraResource.getPath());
        }
    }

    @PostConstruct
    private void init() {
        final Session           session           = sessionFactory.getInternalSession();
        final NamespaceRegistry namespaceRegistry = NamespaceTools.getNamespaceRegistry(session);

        ensureSwordNamespaceRegistration(namespaceRegistry);

        final Container root = getContainer(session, SWORD_ROOT_PATH, SWORD_ROOT_LABEL);
        ensureProperty(namespaceRegistry, root, RdfLexicon.FEDORA_CONFIG_NAMESPACE + "supportsVerbose", SWORD_SUPPORTS_VERBOSE);
        ensureProperty(namespaceRegistry, root, RdfLexicon.FEDORA_CONFIG_NAMESPACE + "supportsNoop", SWORD_SUPPORTS_NOOP);
        ensureProperty(namespaceRegistry, root, RdfLexicon.FEDORA_CONFIG_NAMESPACE + "maxUploadSizeInKb", SWORD_MAX_UPLOAD_SIZE_KB);

        workspaces = getContainer(session, SWORD_WORKSPACES_PATH, "SWORD workspaces");
        getContainer(session, SWORD_COLLECTIONS_PATH, "SWORD collections");
    }

    private void ensureSwordNamespaceRegistration(final NamespaceRegistry namespaceRegistry) {
        try {
            namespaceRegistry.registerNamespace(RDF_PREFIX, NS_SWORD);
        } catch (RepositoryException e) {
            throw new RuntimeException(
                    String.format("Failed to register namespace %s:%s", RDF_PREFIX, NS_SWORD), e);
        }
    }

    private Container getContainer(final Session session, final String path, final String label) {
        try {
            if (!nodeService.exists(session, path)) {
                log.info("Initializing container `{}` at `{}`", label, path);
                final Container c = containerService.findOrCreate(session, path);
                session.save();
                return c;
            }
        } catch (RepositoryException e) {
            throw new RuntimeException("Failed to initialize container " + label, e);
        }
        return null;
    }

    private void ensureProperty(
            final NamespaceRegistry namespaceRegistry,
            final Container container,
            final String name,
            final Object defaultValue) {
        try {
            final Map<String, String> namespaceMapping = emptyMap();
            final String propertyName = getPropertyNameFromPredicate(
                    (org.modeshape.jcr.api.NamespaceRegistry) namespaceRegistry,
                    createProperty(name),
                    namespaceMapping);
            if (!container.getNode().hasProperty(propertyName)) {
                container.getNode().setProperty(propertyName, String.valueOf(defaultValue));
            }
        } catch (RepositoryException e) {
            throw new RuntimeException("Failed to initialize property " + name, e);
        }
    }

}

@@ -2468,140 +2468,8 @@
 0%22;%0A
-    private static final Boolean SWORD_SUPPORTS_VERBOSE   = true;%0A    private static final Boolean SWORD_SUPPORTS_NOOP      = true;%0A
     
@@ -4728,260 +4728,24 @@
 oot,
- RdfLexicon.FEDORA_CONFIG_NAMESPACE + %22supportsVerbose%22, SWORD_SUPPORTS_VERBOSE);%0A        ensureProperty(namespaceRegistry, root, RdfLexicon.FEDORA_CONFIG_NAMESPACE + %22supportsNoop%22, SWORD_SUPPORTS_NOOP);%0A        ensureProperty(namespaceRegistry, root,
+%0A               
  Rdf

Remove `supportsNoop` and `supportsVerbose` properties