/*
 * utils - MultipleDataProvider.java - Copyright © 2010 David Roden
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package net.pterodactylus.util.template;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.StringTokenizer;

/**
 * {@link DataProvider} implementation that can get its data from multiple other
 * {@link DataProvider}s.
 *
 * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
 */
public class MultipleDataProvider extends DataProvider {

	/** The source data providers. */
	private final List<DataProvider> dataProviders = new ArrayList<DataProvider>();

	/** The data stores. */
	private final MultipleDataStore dataStore;

	/**
	 * Creates a new multiple data provider.
	 *
	 * @param dataProviders
	 *            The source data providers
	 */
	public MultipleDataProvider(DataProvider... dataProviders) {
		this.dataProviders.addAll(Arrays.asList(dataProviders));
		List<DataStore> dataStores = new ArrayList<DataStore>();
		for (DataProvider dataProvider : dataProviders) {
			dataStores.add(dataProvider.getDataStore());
		}
		this.dataStore = new MultipleDataStore(dataStores);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public Object getData(String name) throws TemplateException {
		if (name.indexOf('.') == -1) {
			for (DataProvider dataProvider : dataProviders) {
				Object data = dataProvider.getDataStore().get(name);
				if (data != null) {
					return data;
				}
			}
			return null;
		}
		StringTokenizer nameTokens = new StringTokenizer(name, ".");
		Object object = null;
		while (nameTokens.hasMoreTokens()) {
			String nameToken = nameTokens.nextToken();
			if (object == null) {
				for (DataProvider dataProvider : dataProviders) {
					object = dataProvider.getDataStore().get(nameToken);
					if (object != null) {
						break;
					}
				}
			} else {
				Accessor accessor = null;
				for (DataProvider dataProvider : dataProviders) {
					accessor = dataProvider.findAccessor(object.getClass());
					if (accessor != null) {
						break;
					}
				}
				if (accessor != null) {
					object = accessor.get(this, object, nameToken);
				} else {
					throw new TemplateException("no accessor found for " + object.getClass());
				}
			}
			if (object == null) {
				return null;
			}
		}
		return object;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	protected DataStore getDataStore() {
		return dataStore;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void setData(String name, Object data) {
		for (DataProvider dataProvider : dataProviders) {
			dataProvider.setData(name, data);
		}
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	protected Accessor findAccessor(Class<?> clazz) {
		for (DataProvider dataProvider : dataProviders) {
			Accessor accessor = dataProvider.findAccessor(clazz);
			if (accessor != null) {
				return accessor;
			}
		}
		return null;
	}

	/**
	 * A {@link DataStore} implementation that is backed by multiple other
	 * {@link DataStore}s.
	 *
	 * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
	 */
	private static class MultipleDataStore implements DataStore {

		/** The backing data stores. */
		private List<DataStore> dataStores = new ArrayList<DataStore>();

		/**
		 * Creates a new multiple data store.
		 *
		 * @param dataStores
		 *            The backing data stores
		 */
		public MultipleDataStore(List<DataStore> dataStores) {
			this.dataStores.addAll(dataStores);
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public Object get(String name) {
			for (DataStore dataStore : dataStores) {
				Object data = dataStore.get(name);
				if (data != null) {
					return data;
				}
			}
			return null;
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public void set(String name, Object data) {
			for (DataStore dataStore : dataStores) {
				dataStore.set(name, data);
			}
		}

	}

}