/*
 * 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 com.alibaba.metrics;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;

public class AliMetricManager implements IMetricManager {

    @SuppressWarnings("rawtypes")
    final private static SortedMap emptySortedMap = new TreeMap();

    private Map<String, MetricRegistry> metricRegistryMap;

    private volatile boolean enabled;

    public AliMetricManager() {
        metricRegistryMap = new ConcurrentHashMap<String, MetricRegistry>();
        enabled = true;
    }

    @Override
    public Meter getMeter(String group, MetricName name) {
        if (!this.enabled) {
            return MetricManager.NOP_METRIC_MANAGER.getMeter(group, name);
        }
        return getMetricRegistryByGroup(group).meter(name);
    }

    @Override
    public Counter getCounter(String group, MetricName name) {
        if (!this.enabled) {
            return MetricManager.NOP_METRIC_MANAGER.getCounter(group, name);
        }
        return getMetricRegistryByGroup(group).counter(name);
    }

    @Override
    public Histogram getHistogram(String group, MetricName name) {
        if (!this.enabled) {
            return MetricManager.NOP_METRIC_MANAGER.getHistogram(group, name);
        }
        return getMetricRegistryByGroup(group).histogram(name);
    }

    @Override
    public Histogram getHistogram(String group, MetricName name, ReservoirType type) {
        if (!this.enabled) {
            return MetricManager.NOP_METRIC_MANAGER.getHistogram(group, name, type);
        }
        return getMetricRegistryByGroup(group).histogram(name, type);
    }

    @Override
    public Timer getTimer(String group, MetricName name) {
        if (!this.enabled) {
            return MetricManager.NOP_METRIC_MANAGER.getTimer(group, name);
        }
        return getMetricRegistryByGroup(group).timer(name);
    }

    @Override
    public Timer getTimer(String group, MetricName name, ReservoirType type) {
        if (!this.enabled) {
            return MetricManager.NOP_METRIC_MANAGER.getTimer(group, name, type);
        }
        return getMetricRegistryByGroup(group).timer(name, type);
    }

    @Override
    public Compass getCompass(String group, MetricName name) {
        if (!this.enabled) {
            return MetricManager.NOP_METRIC_MANAGER.getCompass(group, name);
        }
        return getMetricRegistryByGroup(group).compass(name);
    }

    @Override
    public Compass getCompass(String group, MetricName name, ReservoirType type) {
        if (!this.enabled) {
            return MetricManager.NOP_METRIC_MANAGER.getCompass(group, name, type);
        }
        return getMetricRegistryByGroup(group).compass(name, type);
    }

    @Override
    public FastCompass getFastCompass(String group, MetricName name) {
        if (!this.enabled) {
            return MetricManager.NOP_METRIC_MANAGER.getFastCompass(group, name);
        }
        return getMetricRegistryByGroup(group).fastCompass(name);
    }

    @Override
    public ClusterHistogram getClusterHistogram(String group, MetricName name, long[] buckets) {
        if (!this.enabled) {
            return MetricManager.NOP_METRIC_MANAGER.getClusterHistogram(group, name, buckets);
        }
        return getMetricRegistryByGroup(group).clusterHistogram(name, buckets);
    }

    @Override
    public List<String> listMetricGroups() {
        if (!this.enabled) {
            return Collections.emptyList();
        }
        List<String> groups = new ArrayList<String>();
        groups.addAll(metricRegistryMap.keySet());
        return groups;
    }

    @Override
    public boolean isEnabled() {
        return enabled;
    }

    @Override
    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }

    @Override
    public Map<String, Set<MetricName>> listMetricNamesByGroup() {
        if (!this.enabled) {
            return Collections.emptyMap();
        }
        Map<String, Set<MetricName>> result = new HashMap<String, Set<MetricName>>();
        for (Map.Entry<String, MetricRegistry> entry : metricRegistryMap.entrySet()) {
            Set<MetricName> metricNames = new TreeSet<MetricName>();
            for (Map.Entry<MetricName, Metric> metricEntry : entry.getValue().getMetrics().entrySet()) {
                metricNames.add(metricEntry.getKey());
            }
            result.put(entry.getKey(), metricNames);
        }
        return result;
    }

    @Override
    public MetricRegistry getMetricRegistryByGroup(String group) {
        if (!metricRegistryMap.containsKey(group)) {
            metricRegistryMap.put(group, new MetricRegistryImpl());
        }
        return metricRegistryMap.get(group);
    }

    @Override
    @SuppressWarnings("unchecked")
    public SortedMap<MetricName, Gauge> getGauges(String group, MetricFilter filter) {
        if (!this.enabled) {
            return emptySortedMap;
        }
        MetricRegistry metricRegistry = this.getMetricRegistryByGroup(group);
        if (metricRegistry == null) {
            return emptySortedMap;
        }
        return metricRegistry.getGauges(filter);
    }

    @Override
    @SuppressWarnings("unchecked")
    public SortedMap<MetricName, Counter> getCounters(String group, MetricFilter filter) {
        if (!this.enabled) {
            return emptySortedMap;
        }

        MetricRegistry metricRegistry = this.getMetricRegistryByGroup(group);
        if (metricRegistry == null) {
            return emptySortedMap;
        }
        return metricRegistry.getCounters(filter);
    }

    @Override
    @SuppressWarnings("unchecked")
    public SortedMap<MetricName, Histogram> getHistograms(String group, MetricFilter filter) {
        if (!this.enabled) {
            return emptySortedMap;
        }

        MetricRegistry metricRegistry = this.getMetricRegistryByGroup(group);
        if (metricRegistry == null) {
            return emptySortedMap;
        }
        return metricRegistry.getHistograms(filter);
    }

    @Override
    @SuppressWarnings("unchecked")
    public SortedMap<MetricName, Meter> getMeters(String group, MetricFilter filter) {
        if (!this.enabled) {
            return emptySortedMap;
        }

        MetricRegistry metricRegistry = this.getMetricRegistryByGroup(group);
        if (metricRegistry == null) {
            return emptySortedMap;
        }
        return metricRegistry.getMeters(filter);
    }

    @Override
    @SuppressWarnings("unchecked")
    public SortedMap<MetricName, Timer> getTimers(String group, MetricFilter filter) {
        if (!this.enabled) {
            return emptySortedMap;
        }

        MetricRegistry metricRegistry = this.getMetricRegistryByGroup(group);
        if (metricRegistry == null) {
            return emptySortedMap;
        }
        return metricRegistry.getTimers(filter);
    }

    @Override
    public SortedMap<MetricName, Compass> getCompasses(String group, MetricFilter filter) {
        if (!this.enabled) {
            return emptySortedMap;
        }

        MetricRegistry metricRegistry = this.getMetricRegistryByGroup(group);
        if (metricRegistry == null) {
            return emptySortedMap;
        }
        return metricRegistry.getCompasses(filter);
    }

    @Override
    public SortedMap<MetricName, FastCompass> getFastCompasses(String group, MetricFilter filter) {
        if (!this.enabled) {
            return emptySortedMap;
        }

        MetricRegistry metricRegistry = this.getMetricRegistryByGroup(group);
        if (metricRegistry == null) {
            return emptySortedMap;
        }
        return metricRegistry.getFastCompasses(filter);
    }

    @Override
    public SortedMap<MetricName, ClusterHistogram> getClusterHistogram(String group, MetricFilter filter) {
        if (!this.enabled) {
            return emptySortedMap;
        }

        MetricRegistry metricRegistry = this.getMetricRegistryByGroup(group);
        if (metricRegistry == null) {
            return emptySortedMap;
        }
        return metricRegistry.getClusterHistograms(filter);
    }

    @Override
    public void register(String group, MetricName name, Metric metric) {
        if (!this.enabled) {
            return;
        }

        MetricRegistry metricRegistry = this.getMetricRegistryByGroup(group);
        metricRegistry.register(name, metric);
    }

    @Override
    public Map<MetricName, Metric> getMetrics(String group) {
        if (!this.enabled) {
            return Collections.emptyMap();
        }

        MetricRegistry metricRegistry = this.metricRegistryMap.get(group);
        if (metricRegistry != null) {
            return metricRegistry.getMetrics();
        }
        return Collections.emptyMap();
    }

    @Override
    public Map<Class<? extends Metric>, Map<MetricName, ? extends Metric>> getCategoryMetrics(String group) {
        return getCategoryMetrics(group, MetricFilter.ALL);
    }

    @Override
    public Map<Class<? extends Metric>, Map<MetricName, ? extends Metric>> getCategoryMetrics(String group,
            MetricFilter filter) {
        if (!this.enabled) {
            return Collections.emptyMap();
        }

        MetricRegistry metricRegistry = this.metricRegistryMap.get(group);
        Map<MetricName, Metric> metrics = metricRegistry.getMetrics();
        return getCategoryMetrics(metrics, filter);
    }

    @Override
    public Map<Class<? extends Metric>, Map<MetricName, ? extends Metric>> getAllCategoryMetrics(MetricFilter filter) {

        if (!this.enabled) {
            return Collections.emptyMap();
        }

        Map<Class<? extends Metric>, Map<MetricName, ? extends Metric>> result = new HashMap<Class<? extends Metric>, Map<MetricName, ? extends Metric>>();

        Map<MetricName, Gauge> gauges = new HashMap<MetricName, Gauge>();
        Map<MetricName, Counter> counters = new HashMap<MetricName, Counter>();
        Map<MetricName, Histogram> histograms = new HashMap<MetricName, Histogram>();
        Map<MetricName, Meter> meters = new HashMap<MetricName, Meter>();
        Map<MetricName, Timer> timers = new HashMap<MetricName, Timer>();
        Map<MetricName, Compass> compasses = new HashMap<MetricName, Compass>();
        Map<MetricName, FastCompass> fastCompasses = new HashMap<MetricName, FastCompass>();
        Map<MetricName, ClusterHistogram> clusterHistogrames = new HashMap<MetricName, ClusterHistogram>();

        for (Entry<String, MetricRegistry> entry : metricRegistryMap.entrySet()) {

            MetricRegistry metricRegistry = entry.getValue();

            Map<MetricName, Metric> metrics = metricRegistry.getMetrics();

            for (Entry<MetricName, Metric> entry1 : metrics.entrySet()) {

                checkAndAdd(entry1, filter, gauges, counters, histograms, meters, timers, compasses, fastCompasses, clusterHistogrames);

            }
        }

        result.put(Gauge.class, gauges);
        result.put(Counter.class, counters);
        result.put(Histogram.class, histograms);
        result.put(Meter.class, meters);
        result.put(Timer.class, timers);
        result.put(Compass.class, compasses);
        result.put(FastCompass.class, fastCompasses);
        result.put(ClusterHistogram.class, clusterHistogrames);

        return result;
    }

    private Map<Class<? extends Metric>, Map<MetricName, ? extends Metric>> getCategoryMetrics(
            Map<MetricName, Metric> metrics, MetricFilter filter) {
        if (!this.enabled) {
            return Collections.emptyMap();
        }

        Map<Class<? extends Metric>, Map<MetricName, ? extends Metric>> result = new HashMap<Class<? extends Metric>, Map<MetricName, ? extends Metric>>();

        Map<MetricName, Gauge> gauges = new HashMap<MetricName, Gauge>();
        Map<MetricName, Counter> counters = new HashMap<MetricName, Counter>();
        Map<MetricName, Histogram> histograms = new HashMap<MetricName, Histogram>();
        Map<MetricName, Meter> meters = new HashMap<MetricName, Meter>();
        Map<MetricName, Timer> timers = new HashMap<MetricName, Timer>();
        Map<MetricName, Compass> compasses = new HashMap<MetricName, Compass>();
        Map<MetricName, FastCompass> fastCompasses = new HashMap<MetricName, FastCompass>();
        Map<MetricName, ClusterHistogram> clusterHistogrames = new HashMap<MetricName, ClusterHistogram>();

        for (Map.Entry<MetricName, Metric> entry : metrics.entrySet()) {
            checkAndAdd(entry, filter, gauges, counters, histograms, meters, timers, compasses, fastCompasses, clusterHistogrames);
        }

        result.put(Gauge.class, gauges);
        result.put(Counter.class, counters);
        result.put(Histogram.class, histograms);
        result.put(Meter.class, meters);
        result.put(Timer.class, timers);
        result.put(Compass.class, compasses);
        result.put(FastCompass.class, fastCompasses);
        result.put(ClusterHistogram.class, clusterHistogrames);

        return result;
    }

    private void checkAndAdd(Map.Entry<MetricName, Metric> entry, MetricFilter filter, Map<MetricName, Gauge> gauges,
            Map<MetricName, Counter> counters, Map<MetricName, Histogram> histograms, Map<MetricName, Meter> meters,
            Map<MetricName, Timer> timers, Map<MetricName, Compass> compasses, Map<MetricName, FastCompass> fastCompasses, Map<MetricName, ClusterHistogram> clusterHistogrames) {

        MetricName metricName = entry.getKey();
        Metric metric = entry.getValue();
        if (metric instanceof Gauge && filter.matches(metricName, metric)) {
            gauges.put(metricName, (Gauge) metric);
        } else if (metric instanceof Counter && filter.matches(metricName, metric)) {
            counters.put(metricName, (Counter) metric);
        } else if (metric instanceof Histogram && filter.matches(metricName, metric)) {
            histograms.put(metricName, (Histogram) metric);
        } else if (metric instanceof Meter && filter.matches(metricName, metric)) {
            meters.put(metricName, (Meter) metric);
        } else if (metric instanceof Timer && filter.matches(metricName, metric)) {
            timers.put(metricName, (Timer) metric);
        } else if (metric instanceof Compass && filter.matches(metricName, metric)) {
            compasses.put(metricName, (Compass) metric);
        } else if (metric instanceof FastCompass && filter.matches(metricName, metric)) {
            fastCompasses.put(metricName, (FastCompass) metric);
        } else if (metric instanceof ClusterHistogram && filter.matches(metricName, metric)) {
            clusterHistogrames.put(metricName, (ClusterHistogram) metric);
        } else if (metric instanceof DynamicMetricSet) {
            DynamicMetricSet dynamicMetricSet = (DynamicMetricSet) metric;
            Map<MetricName, Metric> dynamicMetrics = dynamicMetricSet.getDynamicMetrics();
            for (Map.Entry<MetricName, Metric> dynamicMetricEntry : dynamicMetrics.entrySet()) {
                checkAndAdd(dynamicMetricEntry, filter, gauges, counters, histograms, meters, timers, compasses, fastCompasses, clusterHistogrames);
            }
        }
    }

    @Override
    public void clear() {
        metricRegistryMap.clear();
    }

}
