/*******************************************************************************
 * Copyright (c) 2017, 2019 IBM Corporation 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:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.equinox.internal.ds;

import java.util.*;
import java.util.Map.Entry;
import org.apache.felix.scr.*;
import org.osgi.framework.*;
import org.osgi.framework.dto.ServiceReferenceDTO;
import org.osgi.service.component.ComponentConstants;
import org.osgi.service.component.ComponentInstance;
import org.osgi.service.component.runtime.ServiceComponentRuntime;
import org.osgi.service.component.runtime.dto.*;

@Deprecated
public class ScrServiceImpl implements ScrService {
	final ServiceComponentRuntime scr;
	final BundleContext context;

	ScrServiceImpl(ServiceComponentRuntime scr, BundleContext context) {
		this.scr = scr;
		this.context = context.getBundle(Constants.SYSTEM_BUNDLE_LOCATION).getBundleContext();
	}

	@Override
	public Component[] getComponents() {
		return toComponents(scr.getComponentDescriptionDTOs());
	}

	private Component[] toComponents(Collection<ComponentDescriptionDTO> componentDescriptionDTOs) {
		Collection<Component> components = new ArrayList<Component>();
		for (ComponentDescriptionDTO dto : componentDescriptionDTOs) {
			Component component = toComponent(dto);
			if (component != null) {
				components.add(component);
			}
		}
		return components.isEmpty() ? null : components.toArray(new Component[0]);
	}

	@Override
	public Component getComponent(long componentId) {
		for (ComponentDescriptionDTO dto : scr.getComponentDescriptionDTOs()) {
			Long id = (Long) dto.properties.get(ComponentConstants.COMPONENT_ID);
			if (componentId == id) {
				return toComponent(dto);
			}
		}
		return null;
	}

	private Component toComponent(ComponentDescriptionDTO dto) {
		Collection<ComponentConfigurationDTO> configs = scr.getComponentConfigurationDTOs(dto);
		if (configs.isEmpty()) {
			return null;
		}

		return new ComponentImpl(configs.iterator().next());
	}

	@Override
	public Component[] getComponents(String componentName) {
		Collection<ComponentDescriptionDTO> dtos = new ArrayList<ComponentDescriptionDTO>();
		for (ComponentDescriptionDTO dto : scr.getComponentDescriptionDTOs()) {
			if (componentName.equals(dto.name)) {
				dtos.add(dto);
			}
		}
		return toComponents(dtos);
	}

	@Override
	public Component[] getComponents(Bundle bundle) {
		return toComponents(scr.getComponentDescriptionDTOs(bundle));
	}

	ServiceReference<?> getServiceReference(long id) {
		try {
			ServiceReference<?>[] refs = context.getServiceReferences((String) null, "(" + Constants.SERVICE_ID + "=" + id + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
			if (refs != null && refs.length > 0) {
				return refs[0];
			}
		} catch (InvalidSyntaxException e) {
			throw new RuntimeException(e);
		}
		return null;
	}

	class ComponentImpl implements Component {
		private final ComponentConfigurationDTO config;

		ComponentImpl(ComponentConfigurationDTO config) {
			this.config = config;
		}

		@Override
		public long getId() {
			return config.id;
		}

		@Override
		public String getName() {
			return config.description.name;
		}

		@Override
		public int getState() {
			switch (config.state) {
				case ComponentConfigurationDTO.ACTIVE :
					return Component.STATE_ACTIVE;
				case ComponentConfigurationDTO.SATISFIED :
					return Component.STATE_REGISTERED;
				case ComponentConfigurationDTO.UNSATISFIED_CONFIGURATION :
					return Component.STATE_UNSATISFIED;
				case ComponentConfigurationDTO.UNSATISFIED_REFERENCE :
					return Component.STATE_UNSATISFIED;
				default :
					break;
			}
			return Component.STATE_DISABLED;
		}

		@Override
		public Bundle getBundle() {
			return context.getBundle(config.description.bundle.id);
		}

		@Override
		public String getFactory() {
			return config.description.factory;
		}

		@Override
		public boolean isServiceFactory() {
			return Constants.SCOPE_BUNDLE.equals(config.description.scope);
		}

		@Override
		public String getClassName() {
			return config.description.implementationClass;
		}

		@Override
		public boolean isDefaultEnabled() {
			return config.description.defaultEnabled;
		}

		@Override
		public boolean isImmediate() {
			return config.description.immediate;
		}

		@Override
		public String[] getServices() {
			return config.description.serviceInterfaces;
		}

		@SuppressWarnings({"rawtypes", "unchecked"})
		@Override
		public Dictionary getProperties() {
			return new Hashtable(config.description.properties);
		}

		@Override
		public Reference[] getReferences() {
			if (config.description.references.length == 0) {
				return null;
			}
			Map<String, ReferenceDTO> referenceDTOs = new HashMap<String, ReferenceDTO>();
			for (ReferenceDTO reference : config.description.references) {
				referenceDTOs.put(reference.name, reference);
			}
			Map<String, SatisfiedReferenceDTO> satisfiedDTOs = new HashMap<String, SatisfiedReferenceDTO>();
			for (SatisfiedReferenceDTO satisfied : config.satisfiedReferences) {
				satisfiedDTOs.put(satisfied.name, satisfied);
			}
			Map<String, UnsatisfiedReferenceDTO> unsatisfiedDTOs = new HashMap<String, UnsatisfiedReferenceDTO>();
			for (UnsatisfiedReferenceDTO unsatisfied : config.unsatisfiedReferences) {
				unsatisfiedDTOs.put(unsatisfied.name, unsatisfied);
			}
			return toReferences(referenceDTOs, satisfiedDTOs, unsatisfiedDTOs);
		}

		private Reference[] toReferences(Map<String, ReferenceDTO> referenceDTOs, Map<String, SatisfiedReferenceDTO> satisfiedDTOs, Map<String, UnsatisfiedReferenceDTO> unsatisfiedDTOs) {
			Collection<Reference> references = new ArrayList<Reference>();
			for (Entry<String, SatisfiedReferenceDTO> satisfied : satisfiedDTOs.entrySet()) {
				references.add(new SatsifiedReference(satisfied.getValue(), referenceDTOs.get(satisfied.getValue().name)));
			}
			for (Entry<String, UnsatisfiedReferenceDTO> unsatisfied : unsatisfiedDTOs.entrySet()) {
				references.add(new UnsatsifiedReference(unsatisfied.getValue(), referenceDTOs.get(unsatisfied.getValue().name)));
			}
			return references.toArray(new Reference[0]);
		}

		@Override
		public ComponentInstance<Object> getComponentInstance() {
			throw new UnsupportedOperationException("Not supported."); //$NON-NLS-1$
		}

		@Override
		public String getActivate() {
			return config.description.activate;
		}

		@Override
		public boolean isActivateDeclared() {
			return config.description.activate != null;
		}

		@Override
		public String getDeactivate() {
			return config.description.deactivate;
		}

		@Override
		public boolean isDeactivateDeclared() {
			return config.description.deactivate != null;
		}

		@Override
		public String getModified() {
			return config.description.modified;
		}

		@Override
		public String getConfigurationPolicy() {
			return config.description.configurationPolicy;
		}

		@Override
		public void enable() {
			scr.disableComponent(config.description);
		}

		@Override
		public void disable() {
			scr.disableComponent(config.description);
		}
	}

	abstract class ReferenceBase implements Reference {
		protected final ReferenceDTO reference;

		ReferenceBase(ReferenceDTO reference) {
			this.reference = reference;
		}

		@Override
		public String getName() {
			return reference.name;
		}

		@Override
		public String getServiceName() {
			return reference.interfaceName;
		}

		@Override
		public boolean isOptional() {
			return reference.cardinality.startsWith("0"); //$NON-NLS-1$
		}

		@Override
		public boolean isMultiple() {
			return reference.cardinality.startsWith("1"); //$NON-NLS-1$
		}

		@Override
		public boolean isStatic() {
			return "static".equals(reference.policy); //$NON-NLS-1$
		}

		@Override
		public String getTarget() {
			return reference.target;
		}

		@Override
		public String getBindMethodName() {
			return reference.bind;
		}

		@Override
		public String getUnbindMethodName() {
			return reference.unbind;
		}

		@Override
		public String getUpdatedMethodName() {
			return reference.updated;
		}
	}

	class SatsifiedReference extends ReferenceBase {
		private final SatisfiedReferenceDTO satisfied;

		SatsifiedReference(SatisfiedReferenceDTO satisifed, ReferenceDTO reference) {
			super(reference);
			this.satisfied = satisifed;
		}

		@SuppressWarnings("rawtypes")
		@Override
		public ServiceReference[] getServiceReferences() {
			Collection<ServiceReference<?>> serviceReferences = new ArrayList<ServiceReference<?>>();
			for (ServiceReferenceDTO serviceRefDTO : satisfied.boundServices) {
				ServiceReference<?> ref = getServiceReference(serviceRefDTO.id);
				if (ref != null) {
					serviceReferences.add(ref);
				}
			}
			return serviceReferences.isEmpty() ? null : serviceReferences.toArray(new ServiceReference[0]);
		}

		@Override
		public boolean isSatisfied() {
			return true;
		}
	}

	class UnsatsifiedReference extends ReferenceBase {
		private final UnsatisfiedReferenceDTO unsatisfied;

		UnsatsifiedReference(UnsatisfiedReferenceDTO unsatisifed, ReferenceDTO reference) {
			super(reference);
			this.unsatisfied = unsatisifed;
		}

		@Override
		@SuppressWarnings("rawtypes")
		public ServiceReference[] getServiceReferences() {
			Collection<ServiceReference<?>> serviceReferences = new ArrayList<ServiceReference<?>>();
			for (ServiceReferenceDTO serviceRefDTO : unsatisfied.targetServices) {
				ServiceReference<?> ref = getServiceReference(serviceRefDTO.id);
				if (ref != null) {
					serviceReferences.add(ref);
				}
			}
			return serviceReferences.isEmpty() ? null : serviceReferences.toArray(new ServiceReference[0]);
		}

		@Override
		public boolean isSatisfied() {
			return false;
		}
	}

}
