package net.unicon.cas.addons.serviceregistry;

import com.fasterxml.jackson.databind.ObjectMapper;
import net.unicon.cas.addons.support.GuardedBy;
import net.unicon.cas.addons.support.ResourceChangeDetectingEventNotifier;
import net.unicon.cas.addons.support.ThreadSafe;
import org.jasig.cas.services.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.ApplicationListener;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

/**
 * Implementation of <code>ServiceRegistryDao</code> that reads services definition from JSON configuration file at the Spring Application Context
 * initialization time. After un-marshaling services from JSON blob, delegates the storage and retrieval to <code>InMemoryServiceRegistryDaoImpl</code>
 * <p/>
 * This class implements ${link ApplicationListener<ResourceChangeDetectingEventNotifier.ResourceChangedEvent>} to reload services definitions in real-time.
 * This class is thread safe.
 *
 * @author Dmitriy Kopylenko
 * @author Unicon, inc.
 * @since 0.8
 */
@ThreadSafe
public class JsonServiceRegistryDao implements ServiceRegistryDao,
		ApplicationListener<ResourceChangeDetectingEventNotifier.ResourceChangedEvent> {

	@GuardedBy("mutexMonitor")
	private final InMemoryServiceRegistryDaoImpl delegateServiceRegistryDao = new InMemoryServiceRegistryDaoImpl();

	protected final ObjectMapper objectMapper = new ObjectMapper();

	protected final Resource servicesConfigFile;

	private ReloadableServicesManager servicesManager;

	private final Object mutexMonitor = new Object();

	private static final String REGEX_PREFIX = "^";

	protected static final String SERVICES_KEY = "services";

	private static final String SERVICES_ID_KEY = "serviceId";

	protected final Logger logger = LoggerFactory.getLogger(this.getClass());

	public JsonServiceRegistryDao(final Resource servicesConfigFile) {
		this.servicesConfigFile = servicesConfigFile;
	}

	public final void setServicesManager(final ReloadableServicesManager servicesManager) {
		this.servicesManager = servicesManager;
	}

	@Override
	public final RegisteredService save(RegisteredService registeredService) {
		synchronized (this.mutexMonitor) {
			return saveInternal(registeredService);
		}
	}

	@Override
	public final boolean delete(RegisteredService registeredService) {
		synchronized (this.mutexMonitor) {
			return deleteInternal(registeredService);
		}
	}

	@Override
	public final RegisteredService findServiceById(long id) {
		synchronized (this.mutexMonitor) {
			return this.delegateServiceRegistryDao.findServiceById(id);
		}
	}

	@Override
	public final List<RegisteredService> load() {
		synchronized (this.mutexMonitor) {
            return this.delegateServiceRegistryDao.load();
		}
	}

    protected RegisteredService saveInternal(final RegisteredService registeredService) {
        return this.delegateServiceRegistryDao.save(registeredService);
    }

    protected boolean deleteInternal(final RegisteredService registeredService) {
        return this.delegateServiceRegistryDao.delete(registeredService);
    }
	/**
	 * This method is used as a Spring bean loadServices-method
	 * as well as the reloading method when the change in the services definition resource is detected at runtime
	 */
	@SuppressWarnings("unchecked")
	public final List<RegisteredService> loadServices() {
		logger.info("Loading Registered Services from: [ {} ]...", this.servicesConfigFile);
        final List<RegisteredService> resolvedServices = new ArrayList<RegisteredService>();
        try {

            if (this.servicesConfigFile.exists() && this.servicesConfigFile.getFile().length() > 0) {
                final Map<String, List> m = this.objectMapper.readValue(this.servicesConfigFile.getFile(), Map.class);
                final Iterator<Map> i = m.get(SERVICES_KEY).iterator();
                while (i.hasNext()) {
                    final Map<?, ?> record = i.next();
                    final String svcId = ((String) record.get(SERVICES_ID_KEY));
                    final RegisteredService svc = getRegisteredServiceInstance(svcId);
                    if (svc != null) {
                        resolvedServices.add(this.objectMapper.convertValue(record, svc.getClass()));
                        logger.debug("Unmarshaled {}: {}", svc.getClass().getSimpleName(), record);
                    }
                }
            } else {
                logger.warn("Resource [{}] does not exist or has no service definitions.", this.servicesConfigFile.getFilename());
            }
			synchronized (this.mutexMonitor) {
				this.delegateServiceRegistryDao.setRegisteredServices(resolvedServices);
			}
		}
		catch (Throwable e) {
			throw new RuntimeException(e);
		}
        return resolvedServices;
	}

    private boolean isValidRegexPattern(final String pattern) {
        boolean valid = false;
        try {
            if (pattern.startsWith(REGEX_PREFIX)) {
                Pattern.compile(pattern);
                valid = true;
            }
        } catch (final PatternSyntaxException e) {
            logger.debug("Failed to identify [{}] as a regular expression", pattern);
        }
        return valid;
    }

    /**
     * Constructs an instance of {@link RegisteredServiceWithAttributes} based on the
     * syntax of the pattern defined. If the pattern is considered a valid regular expression,
     * an instance of {@link RegexRegisteredServiceWithAttributes} is created. Otherwise,
     * {@link RegisteredServiceWithAttributesImpl}.
     * @see #isValidRegexPattern(String)
     * @param pattern the pattern of the service definition
     * @return  an instance of {@link RegisteredServiceWithAttributes}
     */
    private RegisteredService getRegisteredServiceInstance(final String pattern) {
        if (isValidRegexPattern(pattern)) {
            return new RegexRegisteredServiceWithAttributes();
        }

        return new RegisteredServiceWithAttributesImpl();
    }

    @Override
	public final void onApplicationEvent(final ResourceChangeDetectingEventNotifier.ResourceChangedEvent resourceChangedEvent) {
		try {
			if (!resourceChangedEvent.getResourceUri().equals(this.servicesConfigFile.getURI())) {
				//Not our resource. Just get out of here.
				return;
			}
		}
		catch (final Throwable e) {
			logger.error("An exception is caught while trying to access JSON resource: ", e);
			return;
		}
		logger.debug("Received change event for JSON resource {}. Reloading services...", resourceChangedEvent.getResourceUri());
		synchronized (this.mutexMonitor) {
			loadServices();
			this.servicesManager.reload();
		}
	}

	/**
	 * Spring infrastructure class to support circular references DI of ReloadableServicesManager and JsonServiceRegistryDao
	 * required to make real-time reloading behavior work with disabling the default CAS periodic polling
	 * reloading behavior which avoids unnecessary additional reloading.
	 */
	@Component
	public static class ServicesManagerInjectableBeanPostProcessor implements BeanFactoryPostProcessor {
		@Override
		public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
			ReloadableServicesManager servicesManager;
			JsonServiceRegistryDao serviceRegistryDao;
			try {
				servicesManager = beanFactory.getBean(ReloadableServicesManager.class);
				serviceRegistryDao = beanFactory.getBean(JsonServiceRegistryDao.class);
			}
			catch (NoSuchBeanDefinitionException e) {
				return;
			}
			serviceRegistryDao.setServicesManager(servicesManager);
		}
	}
}

@@ -784,56 +784,8 @@
 ent;
-%0Aimport org.springframework.util.AntPathMatcher;
 %0A%0Aim

Remove unused import