/*
 * 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.netbeans.modules.java.source.queries.spi;

import java.net.URL;
import org.netbeans.api.annotations.common.NonNull;
import org.netbeans.modules.java.source.queries.APIAccessor;
import org.netbeans.modules.java.source.queries.api.QueryException;
import org.netbeans.modules.java.source.queries.SPIAccessor;
import org.netbeans.modules.java.source.queries.api.Function;
import org.netbeans.modules.java.source.queries.api.Queries;
import org.netbeans.modules.java.source.queries.api.Updates;

/**
 * SPI for the {@link Queries} and {@link Updates}.
 * The implementation is registered in META-INF/services.
 * @author Tomas Zezula
 */
public interface QueriesController {
    /**
     * Performs the query.
     * @param ctx the query {@link Context} holding the
     * query function and query parameter
     * @return the result of the query function
     * @throws IOException in case of error
     */
    <R> R runQuery(@NonNull Context<R> ctx) throws QueryException;

    /**
     * Performs the modification.
     * @param ctx the modification {@link Context} holding the
     * modification function and modification parameter
     * @return the result of the query function, true means changes
     * were applied, false changes were rolled back.
     * @throws IOException in case of error
     */
    boolean runUpdate(@NonNull Context<Boolean> ctx) throws QueryException;

    /**
     * Context of the query or modification
     */
    public static final class Context<R> {

        static {
            SPIAccessor.setInstance(new Accessor());
        }

        private final SPIFnc<?, R> toRun;
        private final URL forURL;

        private Context (
            @NonNull final URL forURL,
            @NonNull final SPIFnc<?, R> toRun) {
            assert forURL != null;
            assert toRun != null;
            this.toRun = toRun;
            this.forURL = forURL;
        }

        /**
         * Return the {@link URL} of a file for which the {@link Queries} or
         * {@link Updates} instance was created.
         * @return the file
         */
        public URL getURL(){
            return forURL;
        }

        /**
         * Performs the query function on query parameter.
         * using the provided {@link ModelOperations}
         * @param op the {@link ModelOperations} SPI to used to
         * perform the operation.
         * @return the query function result
         */
        public R execute(ModelOperations op) throws QueryException {
            return toRun.apply(op);
        }

        private static class Accessor extends SPIAccessor {
            @Override
            public <P extends Queries,R>  QueriesController.Context<R> createContext(
                final Function<P,R> fnc,
                final P param) {
                return new Context<R>(param.getURL(), new SPIFnc<P, R>(fnc, param));
            }
        }

        private static class SPIFnc<P extends Queries, R> implements Function<ModelOperations, R>{

            private final Function<P,R> fnc;
            private final P param;

            private SPIFnc(
                @NonNull final Function<P,R> fnc,
                @NonNull final P param) {
                assert fnc != null;
                assert param != null;
                this.fnc = fnc;
                this.param = param;
            }

            @Override
            public R apply(@NonNull final ModelOperations spi) throws QueryException {
                APIAccessor.getInstance().attach(param, spi);
                try {
                    return fnc.apply(param);
                } finally {
                    APIAccessor.getInstance().detach(param);
                }
            }
        }
    }
}
