/*
 * 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 org.apache.jmeter.visualizers;

import java.io.Serializable;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import org.apache.jmeter.monitor.model.ObjectFactory;
import org.apache.jmeter.monitor.model.Status;
import org.apache.jmeter.monitor.util.Stats;
import org.apache.jmeter.protocol.http.sampler.HTTPSampleResult;
import org.apache.jmeter.samplers.Clearable;
import org.apache.jmeter.samplers.SampleResult;

public class MonitorAccumModel implements Clearable, Serializable {

	private HashMap MAP;

	/**
	 * we use this to set the current monitorModel so that we can save the stats
	 * to the resultcolllector.
	 */
	private MonitorModel CURRENT;

	private List listeners;

	/**
	 * By default, we set the default to 800
	 */
	private int DEFAULT_BUFFER = 800;

	/**
	 * 
	 */
	public MonitorAccumModel() {
		MAP = new HashMap();
		listeners = new LinkedList();
	}

	public int getBufferSize() {
		return DEFAULT_BUFFER;
	}

	public void setBufferSize(int buffer) {
		DEFAULT_BUFFER = buffer;
	}

	/**
	 * Added this method we that we can save the calculated stats.
	 * 
	 * @return current sample
	 */
	public MonitorModel getLastSample() {
		return this.CURRENT;
	}

	/**
	 * Method will look up the server in the map. The MonitorModel will be added
	 * to an existing list, or a new one will be created.
	 * 
	 * @param model
	 */
	public void addSample(MonitorModel model) {
		this.CURRENT = model;
		if (MAP.containsKey(model.getURL())) {
			List newlist = updateArray(model, (List) MAP.get(model.getURL()));
			MAP.put(model.getURL(), newlist);
		} else {
			List samples = Collections.synchronizedList(new LinkedList());
			samples.add(model);
			MAP.put(model.getURL(), samples);
		}
	}

	/**
	 * We want to keep only 240 entries for each server, so we handle the object
	 * array ourselves.
	 * 
	 * @param model
	 */
	private List updateArray(MonitorModel model, List list) {
		if (list.size() < DEFAULT_BUFFER) {
			list.add(model);
		} else {
			list.add(model);
			list.remove(0);
		}
		return list;
	}

	/**
	 * Get all MonitorModels matching the URL.
	 * 
	 * @param url
	 * @return list
	 */
	public List getAllSamples(String url) {
		if (!MAP.containsKey(url)) {
			return Collections.synchronizedList(new LinkedList());
		} else {
			return (List) MAP.get(url);
		}
	}

	/**
	 * Get the MonitorModel matching the url.
	 * 
	 * @param url
	 * @return list
	 */
	public MonitorModel getSample(String url) {
		if (MAP.containsKey(url)) {
			ArrayList list = (ArrayList) MAP.get(url);
			return (MonitorModel) list.get(0);
		} else {
			return null;
		}
	}

	/**
	 * Method will try to parse the response data. If the request was a monitor
	 * request, but the response was incomplete, bad or the server refused the
	 * connection, we will set the server's health to "dead". If the request was
	 * not a monitor sample, the method will ignore it.
	 * 
	 * @param sample
	 */
	public void addSample(SampleResult sample) {
		URL surl = null;
		if (sample instanceof HTTPSampleResult) {
			surl = ((HTTPSampleResult) sample).getURL();
    		// String rescontent = new String(sample.getResponseData());
    		if (sample.isResponseCodeOK() && ((HTTPSampleResult) sample).isMonitor()) {
    			ObjectFactory of = ObjectFactory.getInstance();
    			Status st = of.parseBytes(sample.getResponseData());
    			if (st != null && surl != null) {// surl can be null if read from a file
    				MonitorStats stat = new MonitorStats(Stats.calculateStatus(st), Stats.calculateLoad(st), 0, Stats
    						.calculateMemoryLoad(st), Stats.calculateThreadLoad(st), surl.getHost(), String.valueOf(surl
    						.getPort()), surl.getProtocol(), System.currentTimeMillis());
    				MonitorModel mo = new MonitorModel(stat);
    				this.addSample(mo);
    				notifyListeners(mo);
    			} else {
    				noResponse(surl);
    			}
    		} else if (((HTTPSampleResult) sample).isMonitor()) {
    			noResponse(surl);
    		}
        }
	}

	/**
	 * If there is no response from the server, we create a new MonitorStats
	 * object with the current timestamp and health "dead".
	 * 
	 * @param url
	 */
	public void noResponse(URL url) {
		notifyListeners(createNewMonitorModel(url));
	}

	/**
	 * Method will return a new MonitorModel object with the given URL. This is
	 * used when the server fails to respond fully, or is dead.
	 * 
	 * @param url
	 * @return new MonitorModel
	 */
	public MonitorModel createNewMonitorModel(URL url) {
		MonitorStats stat = new MonitorStats(Stats.DEAD, 0, 0, 0, 0, url.getHost(), String.valueOf(url.getPort()), url
				.getProtocol(), System.currentTimeMillis());
		MonitorModel mo = new MonitorModel(stat);
		return mo;
	}

	/**
	 * Clears everything except the listener. Do not clear the listeners. If we
	 * clear listeners, subsequent "run" will not notify the gui of data
	 * changes.
	 */
	public void clearData() {
		Iterator itr = this.MAP.keySet().iterator();
		while (itr.hasNext()) {
			List lt = (List) this.MAP.get(itr.next());
			lt.clear();
		}
		this.MAP.clear();
	}

	/**
	 * notify the listeners with the MonitorModel object.
	 * 
	 * @param model
	 */
	public void notifyListeners(MonitorModel model) {
		for (int idx = 0; idx < listeners.size(); idx++) {
			MonitorListener ml = (MonitorListener) listeners.get(idx);
			ml.addSample(model);
		}
	}

	/**
	 * Add a listener. When samples are added, the class will notify the
	 * listener of the change.
	 * 
	 * @param listener
	 */
	public void addListener(MonitorListener listener) {
		listeners.add(listener);
	}
}
