/*
 * 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.plugins.index.importer;


import org.apache.jackrabbit.oak.api.jmx.IndexStatsMBean;
import org.apache.jackrabbit.oak.plugins.index.AsyncIndexInfo;
import org.apache.jackrabbit.oak.plugins.index.AsyncIndexInfoService;
import org.apache.jackrabbit.oak.stats.Clock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static org.apache.jackrabbit.guava.common.base.Preconditions.checkNotNull;

/**
 * Lock implementation for single node setup like for SegmentNodeStore
 * It works by check async indexer status via IndexStatsMBean and
 * then aborting it if found to be running
 */
public class AbortingIndexerLock implements AsyncIndexerLock<SimpleToken> {
    public static final int TIMEOUT_SECONDS = 300;
    private final Logger log = LoggerFactory.getLogger(getClass());
    private final AsyncIndexInfoService infoService;
    private final Clock clock;

    public AbortingIndexerLock(AsyncIndexInfoService infoService) {
        this(infoService, Clock.SIMPLE);
    }

    public AbortingIndexerLock(AsyncIndexInfoService infoService, Clock clock) {
        this.infoService = infoService;
        this.clock = clock;
    }

    @Override
    public SimpleToken lock(String asyncIndexerLane) {
        IndexStatsMBean mbean = getIndexStatsMBean(asyncIndexerLane);

        if (IndexStatsMBean.STATUS_RUNNING.equals(mbean.getStatus())){
            log.info("Aborting current indexing run of async indexer for lane [{}]", asyncIndexerLane);
        }

        mbean.abortAndPause();
        retry(mbean, TIMEOUT_SECONDS, 1000);
        log.info("Aborted and paused async indexer for lane [{}]", asyncIndexerLane);
        return new SimpleToken(asyncIndexerLane);
    }

    @Override
    public void unlock(SimpleToken token) {
        getIndexStatsMBean(token.laneName).resume();
        log.info("Resumed async indexer for lane [{}]", token.laneName);
    }

    private IndexStatsMBean getIndexStatsMBean(String asyncIndexerLane) {
        AsyncIndexInfo info = infoService.getInfo(asyncIndexerLane);
        checkNotNull(info, "No AsyncIndexInfo found for lane [%s]", asyncIndexerLane);
        IndexStatsMBean mbean = info.getStatsMBean();
        return checkNotNull(mbean, "No IndexStatsMBean associated with [%s]", asyncIndexerLane);
    }

    private void retry(IndexStatsMBean mbean, int timeoutSeconds, int intervalBetweenTriesMsec) {
        long timeout = clock.getTime() + timeoutSeconds * 1000L;
        while (clock.getTime() < timeout) {
            try {
                if (!IndexStatsMBean.STATUS_RUNNING.equals(mbean.getStatus())) {
                    return;
                }
            } catch (Exception e) {
                throw new RuntimeException(e);
            }

            try {
                int delta = (int) (timeout -  clock.getTime() / 1000);
                log.info("Async indexer for lane [{}] found to be running. Would wait for {} seconds " +
                        "more for it to stop", mbean.getName(), delta);
                Thread.sleep(intervalBetweenTriesMsec);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                return;
            }
        }

        throw new IllegalStateException("RetryLoop failed, condition is false after " + timeoutSeconds + " seconds");
    }
}

final class SimpleToken implements AsyncIndexerLock.LockToken {
    final String laneName;

    SimpleToken(String laneName) {
        this.laneName = laneName;
    }
}
