/*
 * 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.oak.run;

import static java.util.Arrays.asList;
import static org.apache.jackrabbit.JcrConstants.JCR_UUID;
import static org.apache.jackrabbit.oak.segment.file.FileStoreBuilder.fileStoreBuilder;

import java.io.File;
import java.io.IOException;
import java.util.List;

import joptsimple.OptionParser;
import joptsimple.OptionSet;
import joptsimple.OptionSpec;
import org.apache.jackrabbit.oak.api.Type;
import org.apache.jackrabbit.oak.commons.PathUtils;
import org.apache.jackrabbit.oak.plugins.index.IndexConstants;
import org.apache.jackrabbit.oak.run.commons.Command;
import org.apache.jackrabbit.oak.segment.SegmentNodeBuilder;
import org.apache.jackrabbit.oak.segment.SegmentNodeState;
import org.apache.jackrabbit.oak.segment.file.FileStore;
import org.apache.jackrabbit.oak.spi.nodetype.NodeTypeConstants;
import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class CompositePrepareCommand implements Command {

    @Override
    public void execute(String... args) throws Exception {
        OptionParser parser = new OptionParser();

        OptionSpec<?> help = parser
                .acceptsAll(asList("h", "?", "help"), "show help")
                .forHelp();
        OptionSpec<String> paths = parser.accepts("paths", "a list of paths to transform from nt:resource to oak:Resource")
                .withRequiredArg()
                .ofType(String.class)
                .withValuesSeparatedBy(',')
                .defaultsTo("/apps", "/libs");
        OptionSpec<File> storeO = parser.nonOptions("path to segment store (required)")
                .ofType(File.class);
        OptionSet options = parser.parse(args);

        if (options.has(help)) {
            parser.printHelpOn(System.out);
            System.exit(0);
        }

        File store = storeO.value(options);

        if (store == null) {
            parser.printHelpOn(System.out);
            System.exit(1);
        }

        FileStore fs = fileStoreBuilder(store)
            .withStrictVersionCheck(true)
            .build();
        try {
            new OakResourceTransformer(fs, paths.values(options)).transform();
        } finally {
            fs.close();
        }
    }

    public static class OakResourceTransformer {

        private static final Logger LOG = LoggerFactory.getLogger(CompositePrepareCommand.class);

        private final FileStore fileStore;

        private final List<String> paths;

        private int totalNodes;

        private int modifiedNodes;

        public OakResourceTransformer(FileStore fileStore, List<String> paths) {
            this.paths = paths;
            this.fileStore = fileStore;
        }

        public void transform() throws InterruptedException, IOException {
            SegmentNodeState headNodeState = fileStore.getHead();
            SegmentNodeBuilder superRootBuilder = headNodeState.builder();
            for (String cp : superRootBuilder.getChildNode("checkpoints").getChildNodeNames()) {
                LOG.info("Transforming checkpoint {}", cp);
                NodeBuilder cpRoot = superRootBuilder.getChildNode("checkpoints").getChildNode(cp).getChildNode("root");
                transformRootBuilder(cpRoot);
            }
            LOG.info("Transforming root");
            transformRootBuilder(superRootBuilder.getChildNode("root"));
            fileStore.getRevisions().setHead(headNodeState.getRecordId(), superRootBuilder.getNodeState().getRecordId());
            fileStore.flush();
        }

        private void transformRootBuilder(NodeBuilder rootBuilder) {
            for (String p : paths) {
                NodeBuilder builder = rootBuilder;
                for (String e : PathUtils.elements(p)) {
                    builder = builder.getChildNode(e);
                }
                if (builder.exists()) {
                    totalNodes = modifiedNodes = 0;
                    LOG.info("  path: {}", p);
                    NodeBuilder indexData = rootBuilder
                            .child(IndexConstants.INDEX_DEFINITIONS_NAME)
                            .child("uuid")
                            .child(IndexConstants.INDEX_CONTENT_NODE_NAME);
                    transformBuilder(builder, indexData);
                    LOG.info("    all nodes: {}, updated nodes: {}", totalNodes, modifiedNodes);
                }
            }
        }

        private void transformBuilder(NodeBuilder builder, NodeBuilder uuidIndexData) {
            String type = builder.getName(NodeTypeConstants.JCR_PRIMARYTYPE);
            if (NodeTypeConstants.NT_RESOURCE.equals(type)) {
                String index = builder.getString(JCR_UUID);
                uuidIndexData.getChildNode(index).remove();

                builder.setProperty(NodeTypeConstants.JCR_PRIMARYTYPE, NodeTypeConstants.NT_OAK_RESOURCE, Type.NAME);
                builder.removeProperty(JCR_UUID);

                modifiedNodes++;
            }
            for (String child : builder.getChildNodeNames()) {
                transformBuilder(builder.getChildNode(child), uuidIndexData);
            }
            totalNodes++;
        }
    }

}
