/*******************************************************************************
 * Copyright (c) 2011, 2017 BestSolution.at and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 * Tom Schindl <tom.schindl@bestsolution.at> - initial API and implementation
 ******************************************************************************/
package org.eclipse.e4.tools.services.impl;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.annotation.PreDestroy;
import javax.inject.Inject;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.e4.tools.services.IResourcePool;
import org.eclipse.e4.tools.services.IResourceProviderService;
import org.eclipse.e4.tools.services.IResourceService;
import org.eclipse.e4.tools.services.ToolsServicesConstants;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Resource;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;

public class ResourceService implements IResourceService {
	private enum Type {
		IMAGE, FONT, COLOR
	}

	static class PooledResource<T extends Resource> implements
	IPooledResource<T> {
		private final Display display;
		private int count;
		private T resource;
		private String id;
		private ResourceService resourceService;

		PooledResource(Display display, ResourceService resourceService,
				String id, T resource) {
			this.display = display;
			this.id = id;
			this.count = 1;
			this.resourceService = resourceService;
			this.resource = resource;
		}

		@Override
		public String getId() {
			return id;
		}

		@Override
		public T getResource() {
			return resource;
		}

		@Override
		public void dispose() {
			this.count--;
			if (this.count == 0) {
				resourceService.removePooledResource(this);
				if (resource != null) {
					resource.dispose();
				}
				resource = null;
				id = null;
				resourceService = null;
			}
		}
	}

	public static class ResourcePool implements IDiposeableResourcePool {
		private IResourceService resourceService;

		private List<IPooledResource<Image>> pooledImages = new ArrayList<>();
		private List<IPooledResource<Font>> pooledFonts = new ArrayList<>();
		private List<IPooledResource<Color>> pooledColors = new ArrayList<>();
		private final Display display;

		@Inject
		public ResourcePool(IResourceService resourceService, Display display) {
			this.display = display;
			this.resourceService = resourceService;
		}

		@Override
		public Image getImage(String key) throws CoreException {
			if (resourceService == null) {
				throw new CoreException(
						new Status(IStatus.ERROR,
								ToolsServicesConstants.PLUGIN_ID,
								Messages.ResourceService_PoolDisposed));
			}
			IPooledResource<Image> image = null;

			for (final IPooledResource<Image> img : pooledImages) {
				if (img.getId().equals(key)) {
					image = img;
				}
			}
			if (image == null) {
				image = resourceService.getImage(display, key);
				pooledImages.add(image);
			}

			return image.getResource();
		}

		@Override
		public Font getFont(String key) throws CoreException {
			if (resourceService == null) {
				throw new CoreException(
						new Status(IStatus.ERROR,
								ToolsServicesConstants.PLUGIN_ID,
								Messages.ResourceService_PoolDisposed));
			}

			IPooledResource<Font> font = null;
			for (final IPooledResource<Font> fon : pooledFonts) {
				if (fon.getId().equals(key)) {
					font = fon;
				}
			}
			if (font == null) {
				font = resourceService.getFont(display, key);
				pooledFonts.add(font);
			}
			return font.getResource();
		}

		@Override
		public Color getColor(String key) throws CoreException {
			if (resourceService == null) {
				throw new CoreException(
						new Status(IStatus.ERROR,
								ToolsServicesConstants.PLUGIN_ID,
								Messages.ResourceService_PoolDisposed));
			}
			IPooledResource<Color> color = null;

			for (final IPooledResource<Color> col : pooledColors) {
				if (col.getId().equals(key)) {
					color = col;
				}
			}

			if (color == null) {
				color = resourceService.getColor(display,
						key);
				pooledColors.add(color);
			}
			return color.getResource();
		}

		@Override
		public Image getImageUnchecked(String key) {
			try {
				return getImage(key);
			} catch (final CoreException e) {
				return null;
			}
		}

		@Override
		public Font getFontUnchecked(String key) {
			try {
				return getFont(key);
			} catch (final CoreException e) {
				return null;
			}
		}

		@Override
		public Color getColorUnchecked(String key) {
			try {
				return getColor(key);
			} catch (final CoreException e) {
				return null;
			}
		}

		@Override
		@PreDestroy
		public void dispose() {
			for (final IPooledResource<Image> img : pooledImages) {
				img.dispose();
			}
			for (final IPooledResource<Font> font : pooledFonts) {
				font.dispose();
			}
			for (final IPooledResource<Color> col : pooledColors) {
				col.dispose();
			}
			resourceService = null;
			pooledImages = null;
			pooledFonts = null;
			pooledColors = null;
		}
	}

	static class DisplayPool {
		private Map<String, PooledResource<Image>> imagePool;
		private Map<String, PooledResource<Color>> colorPool;
		private Map<String, PooledResource<Font>> fontPool;

		public Map<String, PooledResource<Color>> getColorPool() {
			if (colorPool == null) {
				colorPool = new HashMap<>();
			}
			return colorPool;
		}

		public Map<String, PooledResource<Image>> getImagePool() {
			if (imagePool == null) {
				imagePool = new HashMap<>();
			}
			return imagePool;
		}

		public Map<String, PooledResource<Font>> getFontPool() {
			if (fontPool == null) {
				fontPool = new HashMap<>();
			}
			return fontPool;
		}
	}

	private final Map<Display, DisplayPool> displayPool = new HashMap<>();
	// private Map<String, IResourceProviderService> imagekey2providers = new
	// HashMap<String, IResourceProviderService>();
	// private Map<String, IResourceProviderService> fontkey2providers = new
	// HashMap<String, IResourceProviderService>();
	// private Map<String, IResourceProviderService> colorkey2providers = new
	// HashMap<String, IResourceProviderService>();
	private final BundleContext context;

	public ResourceService() {
		final Bundle b = FrameworkUtil.getBundle(ResourceService.class);
		context = b.getBundleContext();
	}

	protected void removePooledResource(PooledResource<?> resource) {
		if (resource.getResource() instanceof Image) {
			displayPool.get(resource.display).getImagePool().remove(resource.getId());
		} else if (resource.getResource() instanceof Color) {
			displayPool.get(resource.display).getColorPool().remove(resource.getId());
		} else if (resource.getResource() instanceof Font) {
			displayPool.get(resource.display).getFontPool().remove(resource.getId());
		}
	}

	@SuppressWarnings("unchecked")
	private <R extends Resource> PooledResource<R> loadResource(
			Display display, String key, Type type) {
		DisplayPool p = displayPool.get(display);
		PooledResource<R> resource = null;

		if (p != null) {
			if (type == Type.IMAGE) {
				resource = (PooledResource<R>) p.getImagePool().get(key);
			} else if (type == Type.COLOR) {
				resource = (PooledResource<R>) p.getColorPool().get(key);
			} else {
				resource = (PooledResource<R>) p.getFontPool().get(key);
			}
		}

		if (resource != null && resource.getResource() != null) {
			resource.count++;
		} else {
			resource = new PooledResource<>(display, this, key,
					(R) lookupResource(display, key, type));

			if (p == null) {
				p = new DisplayPool();
				displayPool.put(display, p);
			}

			if (type == Type.IMAGE) {
				p.getImagePool().put(key, (PooledResource<Image>) resource);
			} else if (type == Type.COLOR) {
				p.getColorPool().put(key, (PooledResource<Color>) resource);
			} else {
				p.getFontPool().put(key, (PooledResource<Font>) resource);
			}

		}

		return resource;
	}

	@SuppressWarnings("unchecked")
	private <R> R lookupResource(Display display, String key, Type type) {

		if (type == Type.IMAGE) {
			final IResourceProviderService provider = lookupOSGI(key);
			if (provider != null) {
				return (R) provider.getImage(display, key);
			}
		} else if (type == Type.COLOR) {
			final IResourceProviderService provider = lookupOSGI(key);
			if (provider != null) {
				return (R) provider.getColor(display, key);
			}

		} else {
			final IResourceProviderService provider = lookupOSGI(key);
			if (provider != null) {
				return (R) provider.getFont(display, key);
			}
		}
		throw new IllegalArgumentException(Messages.ResourceService_NoProvider + key
				+ "'."); //$NON-NLS-1$
	}

	private IResourceProviderService lookupOSGI(String key) {
		try {
			final Collection<ServiceReference<IResourceProviderService>> refs = context
					.getServiceReferences(IResourceProviderService.class, "(" //$NON-NLS-1$
							+ key + "=*)"); //$NON-NLS-1$
			if (!refs.isEmpty()) {
				final ServiceReference<IResourceProviderService> ref = refs
						.iterator().next();
				return context.getService(ref);
			}
		} catch (final InvalidSyntaxException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return null;
	}

	// public void addProvider(IResourceProviderService provider,
	// Map<String, String> map) {
	// for (Entry<String, String> e : map.entrySet()) {
	// if( e.getKey().startsWith("IMAGE") ) {
	// imagekey2providers.put(e.getKey(), provider);
	// } else if( e.getKey().startsWith("FONT") ) {
	// fontkey2providers.put(e.getKey(), provider);
	// } else if( e.getKey().startsWith("COLOR") ) {
	// colorkey2providers.put(e.getKey(), provider);
	// }
	// }
	// }
	//
	// public void removeProvider(IResourceProviderService provider,
	// Map<String, String> map) {
	// for (Entry<String, String> e : map.entrySet()) {
	// if( e.getKey().startsWith("IMAGE") ) {
	// imagekey2providers.remove(e.getKey());
	// } else if( e.getKey().startsWith("FONT") ) {
	// fontkey2providers.remove(e.getKey());
	// } else if( e.getKey().startsWith("COLOR") ) {
	// colorkey2providers.remove(e.getKey());
	// }
	// }
	// }

	@Override
	public PooledResource<Image> getImage(Display display, String key) {
		return loadResource(display, key, Type.IMAGE);
	}

	@Override
	public PooledResource<Color> getColor(Display display, String key) {
		return loadResource(display, key, Type.COLOR);
	}

	@Override
	public PooledResource<Font> getFont(Display display, String key) {
		return loadResource(display, key, Type.FONT);
	}

	@Override
	public IDiposeableResourcePool getResourcePool(Display display) {
		return new ResourcePool(this, display);
	}

	@Override
	public IResourcePool getControlPool(Control control) {
		final ResourcePool pool = new ResourcePool(this, control.getDisplay());
		control.addDisposeListener(e -> pool.dispose());
		return pool;
	}

}
