/*
 * Copyright (c) 2022 MiLaboratories 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 com.milaboratory.util;

import java.text.DecimalFormat;
import java.util.concurrent.atomic.AtomicLong;

public abstract class AbstractLongProcessReporter implements LongProcessReporter {
    /**
     * 1 second
     */
    public static final long DEFAULT_REPORTING_INTERVAL = 1_000_000_000L;
    public static final double DEFAULT_REPORTING_PROGRESS_DELTA = 0.1;

    /**
     * Counter of long processes
     */
    final AtomicLong counter = new AtomicLong();
    /**
     * Minimal reporting interval in nanoseconds
     */
    final long minimalReportingInterval;
    /**
     * Minimal reporting progress interval
     */
    final double minimalReportingProgressDelta;

    public AbstractLongProcessReporter() {
        this(DEFAULT_REPORTING_INTERVAL, DEFAULT_REPORTING_PROGRESS_DELTA);
    }

    public AbstractLongProcessReporter(long minimalReportingInterval,
                                       double minimalReportingProgressDelta) {
        this.minimalReportingInterval = minimalReportingInterval;
        this.minimalReportingProgressDelta = minimalReportingProgressDelta;
    }

    public abstract void report(long id, String name, double progress);

    @Override
    public LongProcess start(final String name) {
        final long id = counter.getAndIncrement();
        report(id, name, 0.0);
        return new LongProcess() {
            final AtomicLong previousTimestamp = new AtomicLong(System.nanoTime());
            volatile double previousProgress = 0.0;

            @Override
            public void reportStatus(double progress) {
                if (progress < previousProgress + minimalReportingProgressDelta)
                    return;
                long timestamp = System.nanoTime();
                long pt = previousTimestamp.get();
                if (timestamp < pt + minimalReportingInterval)
                    return;
                if (previousTimestamp.compareAndSet(pt, timestamp)) {
                    previousProgress = progress;
                    report(id, name, progress);
                }
            }

            @Override
            public void close() {
                report(id, name, Double.POSITIVE_INFINITY);
            }
        };
    }

    private static final DecimalFormat percentFormat = new DecimalFormat("##.#'%'");

    public static LongProcessReporter stderrReporter() {
        return new AbstractLongProcessReporter() {
            @Override
            public void report(long id, String name, double progress) {
                String status = Double.POSITIVE_INFINITY == progress ? "done" :
                        percentFormat.format(progress * 100.0);
                System.err.println(name + ": " + status);
            }
        };
    }
}
