// 
// Decompiled by Procyon v0.6.0
// 

package io.netty.util.concurrent;

import java.util.Collection;
import java.util.Collections;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicLong;
import io.netty.util.internal.ObjectUtil;
import java.util.concurrent.TimeUnit;

public final class AutoScalingEventExecutorChooserFactory implements EventExecutorChooserFactory
{
    private static final Runnable NO_OOP_TASK;
    private final int minChildren;
    private final int maxChildren;
    private final long utilizationCheckPeriodNanos;
    private final double scaleDownThreshold;
    private final double scaleUpThreshold;
    private final int maxRampUpStep;
    private final int maxRampDownStep;
    private final int scalingPatienceCycles;
    
    public AutoScalingEventExecutorChooserFactory(final int minThreads, final int maxThreads, final long utilizationWindow, final TimeUnit windowUnit, final double scaleDownThreshold, final double scaleUpThreshold, final int maxRampUpStep, final int maxRampDownStep, final int scalingPatienceCycles) {
        this.minChildren = ObjectUtil.checkPositiveOrZero(minThreads, "minThreads");
        this.maxChildren = ObjectUtil.checkPositive(maxThreads, "maxThreads");
        if (minThreads > maxThreads) {
            throw new IllegalArgumentException(String.format("minThreads: %d must not be greater than maxThreads: %d", minThreads, maxThreads));
        }
        this.utilizationCheckPeriodNanos = ObjectUtil.checkNotNull(windowUnit, "windowUnit").toNanos(ObjectUtil.checkPositive(utilizationWindow, "utilizationWindow"));
        this.scaleDownThreshold = ObjectUtil.checkInRange(scaleDownThreshold, 0.0, 1.0, "scaleDownThreshold");
        this.scaleUpThreshold = ObjectUtil.checkInRange(scaleUpThreshold, 0.0, 1.0, "scaleUpThreshold");
        if (scaleDownThreshold >= scaleUpThreshold) {
            throw new IllegalArgumentException("scaleDownThreshold must be less than scaleUpThreshold: " + scaleDownThreshold + " >= " + scaleUpThreshold);
        }
        this.maxRampUpStep = ObjectUtil.checkPositive(maxRampUpStep, "maxRampUpStep");
        this.maxRampDownStep = ObjectUtil.checkPositive(maxRampDownStep, "maxRampDownStep");
        this.scalingPatienceCycles = ObjectUtil.checkPositiveOrZero(scalingPatienceCycles, "scalingPatienceCycles");
    }
    
    @Override
    public EventExecutorChooser newChooser(final EventExecutor[] executors) {
        return new AutoScalingEventExecutorChooser(executors);
    }
    
    static {
        NO_OOP_TASK = (() -> {});
    }
    
    public static final class AutoScalingUtilizationMetric
    {
        private final EventExecutor executor;
        private final AtomicLong utilizationBits;
        
        AutoScalingUtilizationMetric(final EventExecutor executor) {
            this.utilizationBits = new AtomicLong();
            this.executor = executor;
        }
        
        public double utilization() {
            return Double.longBitsToDouble(this.utilizationBits.get());
        }
        
        public EventExecutor executor() {
            return this.executor;
        }
        
        void setUtilization(final double utilization) {
            final long bits = Double.doubleToRawLongBits(utilization);
            this.utilizationBits.lazySet(bits);
        }
    }
    
    private static final class AutoScalingState
    {
        final int activeChildrenCount;
        final long nextWakeUpIndex;
        final EventExecutor[] activeExecutors;
        final EventExecutorChooser activeExecutorsChooser;
        
        AutoScalingState(final int activeChildrenCount, final long nextWakeUpIndex, final EventExecutor[] activeExecutors) {
            this.activeChildrenCount = activeChildrenCount;
            this.nextWakeUpIndex = nextWakeUpIndex;
            this.activeExecutors = activeExecutors;
            this.activeExecutorsChooser = DefaultEventExecutorChooserFactory.INSTANCE.newChooser(activeExecutors);
        }
    }
    
    private final class AutoScalingEventExecutorChooser implements ObservableEventExecutorChooser
    {
        private final EventExecutor[] executors;
        private final EventExecutorChooser allExecutorsChooser;
        private final AtomicReference<AutoScalingState> state;
        private final List<AutoScalingUtilizationMetric> utilizationMetrics;
        
        AutoScalingEventExecutorChooser(final EventExecutor[] executors) {
            this.executors = executors;
            final List<AutoScalingUtilizationMetric> metrics = new ArrayList<AutoScalingUtilizationMetric>(executors.length);
            for (final EventExecutor executor : executors) {
                metrics.add(new AutoScalingUtilizationMetric(executor));
            }
            this.utilizationMetrics = Collections.unmodifiableList((List<? extends AutoScalingUtilizationMetric>)metrics);
            this.allExecutorsChooser = DefaultEventExecutorChooserFactory.INSTANCE.newChooser(executors);
            final AutoScalingState initialState = new AutoScalingState(AutoScalingEventExecutorChooserFactory.this.maxChildren, 0L, executors);
            this.state = new AtomicReference<AutoScalingState>(initialState);
            final ScheduledFuture<?> utilizationMonitoringTask = GlobalEventExecutor.INSTANCE.scheduleAtFixedRate(new UtilizationMonitor(), AutoScalingEventExecutorChooserFactory.this.utilizationCheckPeriodNanos, AutoScalingEventExecutorChooserFactory.this.utilizationCheckPeriodNanos, TimeUnit.NANOSECONDS);
            if (executors.length > 0) {
                executors[0].terminationFuture().addListener(future -> utilizationMonitoringTask.cancel(false));
            }
        }
        
        @Override
        public EventExecutor next() {
            final AutoScalingState currentState = this.state.get();
            if (currentState.activeExecutors.length == 0) {
                this.tryScaleUpBy(1);
                return this.allExecutorsChooser.next();
            }
            return currentState.activeExecutorsChooser.next();
        }
        
        private void tryScaleUpBy(final int amount) {
            if (amount <= 0) {
                return;
            }
            while (true) {
                final AutoScalingState oldState = this.state.get();
                if (oldState.activeChildrenCount >= AutoScalingEventExecutorChooserFactory.this.maxChildren) {
                    return;
                }
                final int canAdd = Math.min(amount, AutoScalingEventExecutorChooserFactory.this.maxChildren - oldState.activeChildrenCount);
                final List<EventExecutor> wokenUp = new ArrayList<EventExecutor>(canAdd);
                final long startIndex = oldState.nextWakeUpIndex;
                for (int i = 0; i < this.executors.length; ++i) {
                    final EventExecutor child = this.executors[(int)Math.abs((startIndex + i) % this.executors.length)];
                    if (wokenUp.size() >= canAdd) {
                        break;
                    }
                    if (child instanceof SingleThreadEventExecutor) {
                        final SingleThreadEventExecutor stee = (SingleThreadEventExecutor)child;
                        if (stee.isSuspended()) {
                            stee.execute(AutoScalingEventExecutorChooserFactory.NO_OOP_TASK);
                            wokenUp.add(stee);
                        }
                    }
                }
                if (wokenUp.isEmpty()) {
                    return;
                }
                final List<EventExecutor> newActiveList = new ArrayList<EventExecutor>(oldState.activeExecutors.length + wokenUp.size());
                Collections.addAll(newActiveList, oldState.activeExecutors);
                newActiveList.addAll(wokenUp);
                final AutoScalingState newState = new AutoScalingState(oldState.activeChildrenCount + wokenUp.size(), startIndex + wokenUp.size(), newActiveList.toArray(new EventExecutor[0]));
                if (this.state.compareAndSet(oldState, newState)) {
                    return;
                }
            }
        }
        
        @Override
        public int activeExecutorCount() {
            return this.state.get().activeChildrenCount;
        }
        
        @Override
        public List<AutoScalingUtilizationMetric> executorUtilizations() {
            return this.utilizationMetrics;
        }
        
        private final class UtilizationMonitor implements Runnable
        {
            private final List<SingleThreadEventExecutor> consistentlyIdleChildren;
            private long lastCheckTimeNanos;
            
            private UtilizationMonitor() {
                this.consistentlyIdleChildren = new ArrayList<SingleThreadEventExecutor>(AutoScalingEventExecutorChooserFactory.this.maxChildren);
            }
            
            @Override
            public void run() {
                if (AutoScalingEventExecutorChooser.this.executors.length == 0 || AutoScalingEventExecutorChooser.this.executors[0].isShuttingDown()) {
                    return;
                }
                final long now = AutoScalingEventExecutorChooser.this.executors[0].ticker().nanoTime();
                long totalTime;
                if (this.lastCheckTimeNanos == 0L) {
                    totalTime = AutoScalingEventExecutorChooserFactory.this.utilizationCheckPeriodNanos;
                }
                else {
                    totalTime = now - this.lastCheckTimeNanos;
                }
                this.lastCheckTimeNanos = now;
                if (totalTime <= 0L) {
                    return;
                }
                int consistentlyBusyChildren = 0;
                this.consistentlyIdleChildren.clear();
                final AutoScalingState currentState = AutoScalingEventExecutorChooser.this.state.get();
                for (int i = 0; i < AutoScalingEventExecutorChooser.this.executors.length; ++i) {
                    final EventExecutor child = AutoScalingEventExecutorChooser.this.executors[i];
                    if (child instanceof SingleThreadEventExecutor) {
                        final SingleThreadEventExecutor eventExecutor = (SingleThreadEventExecutor)child;
                        double utilization = 0.0;
                        if (!eventExecutor.isSuspended()) {
                            long activeTime = eventExecutor.getAndResetAccumulatedActiveTimeNanos();
                            if (activeTime == 0L) {
                                final long lastActivity = eventExecutor.getLastActivityTimeNanos();
                                final long idleTime = now - lastActivity;
                                if (idleTime < totalTime) {
                                    activeTime = totalTime - idleTime;
                                }
                            }
                            utilization = Math.min(1.0, activeTime / (double)totalTime);
                            if (utilization < AutoScalingEventExecutorChooserFactory.this.scaleDownThreshold) {
                                final int idleCycles = eventExecutor.getAndIncrementIdleCycles();
                                eventExecutor.resetBusyCycles();
                                if (idleCycles >= AutoScalingEventExecutorChooserFactory.this.scalingPatienceCycles && eventExecutor.getNumOfRegisteredChannels() <= 0) {
                                    this.consistentlyIdleChildren.add(eventExecutor);
                                }
                            }
                            else if (utilization > AutoScalingEventExecutorChooserFactory.this.scaleUpThreshold) {
                                final int busyCycles = eventExecutor.getAndIncrementBusyCycles();
                                eventExecutor.resetIdleCycles();
                                if (busyCycles >= AutoScalingEventExecutorChooserFactory.this.scalingPatienceCycles) {
                                    ++consistentlyBusyChildren;
                                }
                            }
                            else {
                                eventExecutor.resetIdleCycles();
                                eventExecutor.resetBusyCycles();
                            }
                        }
                        AutoScalingEventExecutorChooser.this.utilizationMetrics.get(i).setUtilization(utilization);
                    }
                }
                final int currentActive = currentState.activeChildrenCount;
                if (consistentlyBusyChildren > 0 && currentActive < AutoScalingEventExecutorChooserFactory.this.maxChildren) {
                    int threadsToAdd = Math.min(consistentlyBusyChildren, AutoScalingEventExecutorChooserFactory.this.maxRampUpStep);
                    threadsToAdd = Math.min(threadsToAdd, AutoScalingEventExecutorChooserFactory.this.maxChildren - currentActive);
                    if (threadsToAdd > 0) {
                        AutoScalingEventExecutorChooser.this.tryScaleUpBy(threadsToAdd);
                        return;
                    }
                }
                boolean changed = false;
                if (!this.consistentlyIdleChildren.isEmpty() && currentActive > AutoScalingEventExecutorChooserFactory.this.minChildren) {
                    int threadsToRemove = Math.min(this.consistentlyIdleChildren.size(), AutoScalingEventExecutorChooserFactory.this.maxRampDownStep);
                    threadsToRemove = Math.min(threadsToRemove, currentActive - AutoScalingEventExecutorChooserFactory.this.minChildren);
                    for (int j = 0; j < threadsToRemove; ++j) {
                        final SingleThreadEventExecutor childToSuspend = this.consistentlyIdleChildren.get(j);
                        if (childToSuspend.trySuspend()) {
                            childToSuspend.resetBusyCycles();
                            childToSuspend.resetIdleCycles();
                            changed = true;
                        }
                    }
                }
                if (changed || currentActive != currentState.activeExecutors.length) {
                    this.rebuildActiveExecutors();
                }
            }
            
            private void rebuildActiveExecutors() {
                AutoScalingState oldState;
                AutoScalingState newState;
                do {
                    oldState = AutoScalingEventExecutorChooser.this.state.get();
                    final List<EventExecutor> active = new ArrayList<EventExecutor>(oldState.activeChildrenCount);
                    for (final EventExecutor executor : AutoScalingEventExecutorChooser.this.executors) {
                        if (!executor.isSuspended()) {
                            active.add(executor);
                        }
                    }
                    final EventExecutor[] newActiveExecutors = active.toArray(new EventExecutor[0]);
                    newState = new AutoScalingState(newActiveExecutors.length, oldState.nextWakeUpIndex, newActiveExecutors);
                } while (!AutoScalingEventExecutorChooser.this.state.compareAndSet(oldState, newState));
            }
        }
    }
}
