/*
 * Copyright 2012-2017 the original author or authors.
 *
 * 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 org.springframework.boot.actuate.metrics.export;

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;

import javax.annotation.PostConstruct;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.util.PatternMatchUtils;

/**
 * Configuration properties for metrics export.
 *
 * @author Dave Syer
 * @author Simon Buettner
 * @since 1.3.0
 */
@ConfigurationProperties(prefix = "spring.metrics.export")
public class MetricExportProperties extends TriggerProperties {

	/**
	 * Specific trigger properties per MetricWriter bean name.
	 */
	private Map<String, SpecificTriggerProperties> triggers = new LinkedHashMap<>();

	private Aggregate aggregate = new Aggregate();

	private Redis redis = new Redis();

	private Statsd statsd = new Statsd();

	@PostConstruct
	public void setUpDefaults() {
		TriggerProperties defaults = this;
		for (Entry<String, SpecificTriggerProperties> entry : this.triggers.entrySet()) {
			String key = entry.getKey();
			SpecificTriggerProperties value = entry.getValue();
			if (value.getNames() == null || value.getNames().length == 0) {
				value.setNames(new String[] { key });
			}
		}
		if (defaults.isSendLatest() == null) {
			defaults.setSendLatest(true);
		}
		if (defaults.getDelayMillis() == null) {
			defaults.setDelayMillis(5000);
		}
		for (TriggerProperties value : this.triggers.values()) {
			if (value.isSendLatest() == null) {
				value.setSendLatest(defaults.isSendLatest());
			}
			if (value.getDelayMillis() == null) {
				value.setDelayMillis(defaults.getDelayMillis());
			}
		}
	}

	/**
	 * Configuration for triggers on individual named writers. Each value can individually
	 * specify a name pattern explicitly, or else the map key will be used if the name is
	 * not set.
	 * @return the writers
	 */
	public Map<String, SpecificTriggerProperties> getTriggers() {
		return this.triggers;
	}

	public Aggregate getAggregate() {
		return this.aggregate;
	}

	public void setAggregate(Aggregate aggregate) {
		this.aggregate = aggregate;
	}

	public Redis getRedis() {
		return this.redis;
	}

	public void setRedis(Redis redis) {
		this.redis = redis;
	}

	public Statsd getStatsd() {
		return this.statsd;
	}

	public void setStatsd(Statsd statsd) {
		this.statsd = statsd;
	}

	/**
	 * Find a matching trigger configuration.
	 * @param name the bean name to match
	 * @return a matching configuration if there is one
	 */
	public TriggerProperties findTrigger(String name) {
		for (SpecificTriggerProperties value : this.triggers.values()) {
			if (PatternMatchUtils.simpleMatch(value.getNames(), name)) {
				return value;
			}
		}
		return this;
	}

	/**
	 * Aggregate properties.
	 */
	public static class Aggregate {

		/**
		 * Prefix for global repository if active. Should be unique for this JVM, but most
		 * useful if it also has the form "a.b" where "a" is unique to this logical
		 * process (this application) and "b" is unique to this physical process. If you
		 * set spring.application.name elsewhere, then the default will be in the right
		 * form.
		 */
		private String prefix = "";

		/**
		 * Pattern that tells the aggregator what to do with the keys from the source
		 * repository. The keys in the source repository are assumed to be period
		 * separated, and the pattern is in the same format, e.g. "d.d.k.d". Here "d"
		 * means "discard" and "k" means "keep" the key segment in the corresponding
		 * position in the source.
		 */
		private String keyPattern = "";

		public String getPrefix() {
			return this.prefix;
		}

		public void setPrefix(String prefix) {
			this.prefix = prefix;
		}

		public String getKeyPattern() {
			return this.keyPattern;
		}

		public void setKeyPattern(String keyPattern) {
			this.keyPattern = keyPattern;
		}

	}

	/**
	 * Redis properties.
	 */
	public static class Redis {

		/**
		 * Prefix for redis repository if active. Should be globally unique across all
		 * processes sharing the same repository.
		 */
		private String prefix = "spring.metrics";

		/**
		 * Key for redis repository export (if active). Should be globally unique for a
		 * system sharing a redis repository across multiple processes.
		 */
		private String key = "keys.spring.metrics";

		public String getPrefix() {
			return this.prefix;
		}

		public void setPrefix(String prefix) {
			this.prefix = prefix;
		}

		public String getKey() {
			return this.key;
		}

		public void setKey(String key) {
			this.key = key;
		}

		public String getAggregatePrefix() {
			// The common case including a standalone aggregator would have a prefix that
			// starts with the end of the key, so strip that bit off and call it the
			// aggregate prefix.
			if (this.key.startsWith("keys.")) {
				String candidate = this.key.substring("keys.".length());
				if (this.prefix.startsWith(candidate)) {
					return candidate;
				}
				return candidate;
			}
			// If the user went off piste, choose something that is safe (not empty) but
			// not the whole prefix (on the assumption that it contains dimension keys)
			if (this.prefix.contains(".")
					&& this.prefix.indexOf(".") < this.prefix.length() - 1) {
				return this.prefix.substring(this.prefix.indexOf(".") + 1);
			}
			return this.prefix;
		}

	}

	/**
	 * Statsd properties.
	 */
	public static class Statsd {

		/**
		 * Host of a statsd server to receive exported metrics.
		 */
		private String host;

		/**
		 * Port of a statsd server to receive exported metrics.
		 */
		private int port = 8125;

		/**
		 * Prefix for statsd exported metrics.
		 */
		private String prefix;

		public String getHost() {
			return this.host;
		}

		public void setHost(String host) {
			this.host = host;
		}

		public int getPort() {
			return this.port;
		}

		public void setPort(int port) {
			this.port = port;
		}

		public String getPrefix() {
			return this.prefix;
		}

		public void setPrefix(String prefix) {
			this.prefix = prefix;
		}

	}

}
