/*
 * 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.connector.mock;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.infinispan.schematic.document.Document;
import org.modeshape.jcr.JcrNtLexicon;
import org.modeshape.jcr.spi.federation.ConnectorChangeSet;
import org.modeshape.jcr.spi.federation.DocumentReader;
import org.modeshape.jcr.spi.federation.DocumentWriter;
import org.modeshape.jcr.value.Name;
import org.modeshape.jcr.value.Path;

/**
 * An extension of the {@link MockConnector} that will automatically create some content and generate events.
 * <p>
 * When a new node is created underneath '/doc{n}/generate' with some name (e.g., 'foo'), the connector automatically creates a
 * new node under '/doc{n}/generate-out' with the same name (e.g., 'foo') and with several child nodes. When a node under
 * '/doc{n}/generate' is removed, then the corresponding node under '/doc{n}/generate-out' will also be removed and events will be
 * produced.
 * </p>
 */
public class MockConnectorWithChanges extends MockConnector {

    @Override
    protected void createInitialNodes() {
        super.createInitialNodes();

        for (Map.Entry<String, Document> entry : new HashMap<String, Document>(documentsByLocation).entrySet()) { // copy
            String pathStr = entry.getKey();
            Path path = pathFrom(pathStr);
            if (path.size() == 1 && isAutoCreatedNode(path)) {
                String parentId = readDocument(entry.getValue()).getDocumentId();

                String id1 = newId();
                Document doc1 = newDocument(id1).setPrimaryType("nt:unstructured").setParent(parentId).document();
                documentsById.put(id1, doc1);
                documentsByLocation.put(pathStr + "/generate", doc1);

                String id2 = newId();
                Document doc2 = newDocument(id1).setPrimaryType("nt:unstructured").setParent(parentId).document();
                documentsById.put(id2, doc2);
                documentsByLocation.put(pathStr + "/generated-out", doc2);

                DocumentWriter parentWriter = writeDocument(entry.getValue());
                parentWriter.addChild(id1, "generate");
                parentWriter.addChild(id2, "generated-out");
                Document parent = parentWriter.document();
                documentsById.put(readDocument(parent).getDocumentId(), parent);
                documentsByLocation.put(pathStr, parent);
            }
        }
    }

    protected boolean isAutoCreatedNode( Path path ) {
        return path.getLastSegment().getName().getLocalName().startsWith("doc");
    }

    protected String pathFromDocumentId( String documentId ) {
        Collection<String> paths = super.getDocumentPathsById(documentId);
        assert !paths.isEmpty();
        return paths.iterator().next();
    }

    @Override
    protected void storedDocument( String documentId,
                                   Document document ) {
        String docPath = pathFromDocumentId(documentId);
        assert docPath != null;
        Path path = pathFrom(docPath);
        if (path.size() == 3 && path.getSegment(1).getName().getLocalName().equals("generate")) {
            List<DocInfo> newDocs = new ArrayList<DocInfo>();
            ConnectorChangeSet changes = newConnectorChangedSet();
            Name topName = path.getSegment(0).getName();
            Name name = path.getSegment(2).getName();

            // Find the parent ...
            String generatedOutPath = "/" + topName.getLocalName() + "/generated-out";
            String generatedOutId = getDocumentId(generatedOutPath);
            Document generatedOutDoc = getDocumentById(generatedOutId);
            DocumentWriter generatedOutWriter = writeDocument(generatedOutDoc);

            // Create a document at '{topName}/generated-out/{name}'
            String newId = newId();
            String newPath = generatedOutPath + "/" + stringFrom(name);
            DocumentWriter writer = newDocument(newId);
            writer.setPrimaryType("nt:unstructured");
            writer.setParent(generatedOutId);
            writer.addProperty("prop1", "value1");
            writer.addProperty("prop2", "value2");
            newDocs.add(new DocInfo(writer.document(), newId, newPath));
            DocumentReader reader = readDocument(writer.document());
            changes.nodeCreated(newId, documentId, newPath, JcrNtLexicon.UNSTRUCTURED, Collections.<Name>emptySet(), reader.getProperties());

            // And some children ...
            for (int i = 0; i != 3; ++i) {
                String childName = "child" + i;
                String childId = newId();
                String childPath = newPath + "/" + childName;
                DocumentWriter childWriter = newDocument(childId);
                childWriter.setPrimaryType("nt:unstructured");
                childWriter.setParent(newId);
                childWriter.addProperty("prop1", "value1");
                childWriter.addProperty("prop2", "value2");
                childWriter.setParent(newId);
                writer.addChild(childId, childName);
                newDocs.add(new DocInfo(childWriter.document(), childId, childPath));
                DocumentReader childReader = readDocument(writer.document());
                changes.nodeCreated(childId, newId, childPath, JcrNtLexicon.UNSTRUCTURED, Collections.<Name>emptySet(), childReader.getProperties());
            }

            for (DocInfo info : newDocs) {
                documentsById.put(info.id, info.doc);
                documentsByLocation.put(info.location, info.doc);
            }

            // Update the parent with a child reference to our new document ...
            generatedOutWriter.addChild(newId, name);
            Document doc = generatedOutWriter.document();
            documentsById.put(generatedOutId, doc);
            documentsByLocation.put(generatedOutPath, doc);

            // Now generate events ...
            changes.publish(null);
        }
    }

    protected static class DocInfo {
        protected Document doc;
        protected String id;
        protected String location;

        protected DocInfo( Document doc,
                           String id,
                           String location ) {
            this.doc = doc;
            this.id = id;
            this.location = location;
        }
    }

    @Override
    protected void preRemoveDocument( String documentId,
                                      Document document ) {
        String docPath = pathFromDocumentId(documentId);
        Path path = pathFrom(docPath);
        if (path.size() == 3 && path.getSegment(1).getName().getLocalName().equals("generate")) {
            ConnectorChangeSet changes = newConnectorChangedSet();

            // Determine the id of the node we'll remove ...
            Name topName = path.getSegment(0).getName();
            Name name = path.getSegment(2).getName();
            String generatedOutPath = "/" + topName.getLocalName() + "/generated-out";
            Document generatedOutDoc = documentsByLocation.get(generatedOutPath);
            String generatedOutId = readDocument(generatedOutDoc).getDocumentId();

            String oldPath = generatedOutPath + "/" + name.getLocalName();
            Document oldDoc = documentsByLocation.get(oldPath);
            String oldId = readDocument(oldDoc).getDocumentId();

            // Remove the child reference from '/doc{n}/generate-out' to the node we'll remove ...
            DocumentWriter generatedOutWriter = writeDocument(generatedOutDoc);
            generatedOutWriter.removeChild(oldId);
            persistDocument(generatedOutId, generatedOutWriter.document());

            // Remove the document at '/doc{n}/generate-out/{name}' ...
            removeDocument(oldId);
            changes.nodeRemoved(oldId, documentId, oldPath, JcrNtLexicon.UNSTRUCTURED, Collections.<Name>emptySet());

            // Remove the child documents, but we don't need to fire events for the subnodes of a deleted node ...
            DocumentReader reader = readDocument(oldDoc);
            for (String childId : reader.getChildrenMap().keySet()) {
                removeDocument(childId);
            }
            changes.publish(null);
        }
    }
}
