/*
 * Copyright (c) 2013-2015 mgm technology partners GmbH
 *
 * 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.mgmtp.perfload.perfalyzer.normalization;

import static com.google.common.collect.Lists.newArrayListWithExpectedSize;
import static com.google.common.collect.Maps.newHashMapWithExpectedSize;
import static com.mgmtp.perfload.perfalyzer.constants.PerfAlyzerConstants.DELIMITER;
import static com.mgmtp.perfload.perfalyzer.util.StrBuilderUtils.appendEscapedAndQuoted;
import static org.apache.commons.lang3.StringUtils.substringBefore;

import java.time.ZonedDateTime;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;

import org.apache.commons.lang3.text.StrBuilder;
import org.apache.commons.lang3.text.StrTokenizer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.mgmtp.perfload.perfalyzer.util.ChannelData;
import com.mgmtp.perfload.perfalyzer.util.PerfMonTypeConfig;
import com.mgmtp.perfload.perfalyzer.util.TimestampNormalizer;

/**
 * Normalizing implementation for perfMon logs.
 *
 * @author rnaegele
 */
public class PerfMonNormalizingStrategy implements NormalizingStrategy {

	private static final String CHANNEL_BASE_NAME = "perfmon";

	private final Logger log = LoggerFactory.getLogger(getClass());

	private final StrTokenizer tokenizer = StrTokenizer.getCSVInstance();
	private final TimestampNormalizer timestampNormalizer;
	private Map<String, Double> firstValues;

	public PerfMonNormalizingStrategy(final TimestampNormalizer timestampNormalizer) {
		this.timestampNormalizer = timestampNormalizer;
		tokenizer.setDelimiterChar('\t');
	}

	@Override
	public List<ChannelData> normalizeLine(final String line) {
		tokenizer.reset(line);
		List<String> tokenList = tokenizer.getTokenList();

		List<ChannelData> result = newArrayListWithExpectedSize(3);
		ZonedDateTime timestamp;
		try {
			timestamp = ZonedDateTime.parse(tokenList.get(0));
		} catch (IllegalArgumentException ex) {
			log.error("Invalid data line: {}", line);
			return result;
		}

		if (!timestampNormalizer.isInRange(timestamp)) {
			log.trace("Skipping perfMon entry. Timestamp not in time range of test: " + timestamp);
			return result;
		}

		String type = tokenList.get(1);

		for (PerfMonTypeConfig typeConfig : PerfMonTypeConfig.values()) {
			Matcher matcher = typeConfig.getPattern().matcher(type);

			if (matcher.matches()) {
				if (typeConfig.isNormalizeValues()) {
					if (firstValues == null) {
						firstValues = newHashMapWithExpectedSize(3);
					}
				}

				for (ValueHolder vh : selectValues(typeConfig, tokenList)) {
					String channelKey = type;
					if (vh.descriptor != null) {
						channelKey += "_" + vh.descriptor;
					}
					long normalizedTimestamp = timestampNormalizer.normalizeTimestamp(timestamp, 0L);

					try {
						// normalize against first value
						double value = typeConfig.factor(Double.parseDouble(vh.value));
						if (typeConfig.isNormalizeValues()) {
							Double firstValue = firstValues.get(channelKey);
							if (firstValue == null) {
								firstValues.put(channelKey, value);
								value = 0d;
							} else {
								value = value - firstValue;
							}
						}

						StrBuilder sb = new StrBuilder();
						appendEscapedAndQuoted(sb, DELIMITER, normalizedTimestamp);
						appendEscapedAndQuoted(sb, DELIMITER, channelKey);
						appendEscapedAndQuoted(sb, DELIMITER, value);

						String resultLine = sb.toString();
						result.add(new ChannelData(CHANNEL_BASE_NAME, channelKey, resultLine));
					} catch (NumberFormatException ex) {
						// in case a line in the perfmon file is incomplete
						log.warn("Could not parse line: " + line, ex);
					}
				}
				break;
			}
		}

		return result;
	}

	private List<ValueHolder> selectValues(final PerfMonTypeConfig typeConfig, final List<String> tokenList) {
		List<ValueHolder> result = newArrayListWithExpectedSize(2);

		switch (typeConfig) {
			case CPU:
			case DOCKER_CPU:
				result.add(new ValueHolder(tokenList.get(2)));
				break;
			case IO:
				result.add(new ValueHolder(tokenList.get(4), "r"));
				result.add(new ValueHolder(tokenList.get(5), "w"));
				break;
			case MEM:
			case SWAP:
			case DOCKER_MEM:
				result.add(new ValueHolder(tokenList.get(3)));
				break;
			case JAVA:
				result.add(new ValueHolder(substringBefore(tokenList.get(tokenList.size() - 2), "%")));
				break;
			case NET:
				result.add(new ValueHolder(tokenList.get(2), "inConn"));
				result.add(new ValueHolder(tokenList.get(3), "outConn"));
				break;
			case TCP:
				result.add(new ValueHolder(tokenList.get(7), "inSeg"));
				result.add(new ValueHolder(tokenList.get(8), "outSeg"));
				result.add(new ValueHolder(tokenList.get(9), "retransSeg"));
				break;
			default:
				throw new IllegalStateException("Invalid perfMon data type");
		}

		return result;
	}

	static class ValueHolder {
		String value;
		String descriptor;

		public ValueHolder(final String value, final String descriptor) {
			this.value = value;
			this.descriptor = descriptor;
		}

		public ValueHolder(final String value) {
			this(value, null);
		}
	}
}
