package eu.dnetlib.enabling.dsManager;

import java.text.ParseException;
import java.util.Date;
import java.util.List;
import java.util.Map;

import javax.annotation.Resource;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.quartz.CronExpression;

import com.google.common.base.Function;
import com.google.common.collect.Lists;

import eu.dnetlib.common.services.AbstractBaseService;
import eu.dnetlib.enabling.datastructures.Datasource;
import eu.dnetlib.enabling.utils.DnetAnnotationUtils;
import eu.dnetlib.enabling.utils.DnetResourceFactory;
import eu.dnetlib.rmi.objects.datasources.BrowsableField;
import eu.dnetlib.rmi.objects.datasources.BrowseTerm;
import eu.dnetlib.rmi.objects.datasources.DatasourceConstants;
import eu.dnetlib.rmi.objects.datasources.IfaceDesc;
import eu.dnetlib.rmi.objects.datasources.RepositoryMapEntry;
import eu.dnetlib.rmi.objects.datasources.SearchInterfacesEntry;
import eu.dnetlib.rmi.objects.datasources.SimpleDatasourceDesc;
import eu.dnetlib.rmi.objects.is.DnetDataStructure;
import eu.dnetlib.rmi.soap.DatasourceManagerService;
import eu.dnetlib.rmi.soap.InformationService;
import eu.dnetlib.rmi.soap.exceptions.DatasourceManagerServiceException;
import eu.dnetlib.rmi.soap.exceptions.InformationServiceException;

public class DatasourceManagerServiceImpl extends AbstractBaseService implements DatasourceManagerService {

	@Resource
	private InformationService informationService;

	private static final Log log = LogFactory.getLog(DatasourceManagerServiceImpl.class);

	@Override
	public boolean addDatasource(final Datasource desc) throws DatasourceManagerServiceException {
		try {
			final DnetDataStructure ds = DnetResourceFactory.createDatastructure(desc.getId(), desc.getOfficialName(), desc);
			final String id = informationService.registerDs(ds);
			log.info("A new datasaource as been registered (assigned id: " + id + ")");
			return true;
		} catch (InformationServiceException e) {
			log.info(e);
			throw new DatasourceManagerServiceException(e);
		}
	}

	@Override
	public boolean deleteDatasource(final String code) throws DatasourceManagerServiceException {
		try {
			return informationService.deleteDsByCode(code, DatasourceConstants.DATASOURCE_TYPE);
		} catch (InformationServiceException e) {
			log.info(e);
			throw new DatasourceManagerServiceException(e);
		}
	}

	@Override
	public Datasource getDatasource(final String code) throws DatasourceManagerServiceException {
		try {
			final DnetDataStructure ds = informationService.getDsByCode(code, DatasourceConstants.DATASOURCE_TYPE);
			return ds.getContentAsObject(Datasource.class);
		} catch (InformationServiceException e) {
			log.info(e);
			throw new DatasourceManagerServiceException(e);
		}
	}

	@Override
	public List<Datasource> listAllDatasources() throws DatasourceManagerServiceException {

		try {
			return Lists.transform(informationService.listDatastructures(DatasourceConstants.DATASOURCE_TYPE), new Function<DnetDataStructure, Datasource>() {

				@Override
				public Datasource apply(final DnetDataStructure ds) {
					try {
						return ds.getContentAsObject(Datasource.class);
					} catch (InformationServiceException e) {
						log.error("Error decoding datasource " + ds, e);
						return null;
					}
				}
			});
		} catch (InformationServiceException e) {
			throw new DatasourceManagerServiceException(e);
		}
	}

	@Override
	public List<Datasource> listDatasourcesUsingFilter(final String compliance,
			final String contentDescription,
			final String iisProcessingWorkflow,
			final String collectedFrom)
			throws DatasourceManagerServiceException {

		if (StringUtils.isNotBlank(iisProcessingWorkflow) || StringUtils.isBlank(collectedFrom)) {
			throw new DatasourceManagerServiceException("Filter not implemented for 'iisProcessingWorkflow' and 'collectedFrom'");
		} else if (StringUtils.isBlank(compliance) && StringUtils.isBlank(contentDescription)) {
			return listAllDatasources();
		} else {
			final List<Datasource> list = Lists.newArrayList();
			for (Datasource ds : listAllDatasources()) {
				for (IfaceDesc ifc : ds.getInterfaces()) {
					boolean b = true;
					b &= compliance == null || compliance.equalsIgnoreCase(ifc.getCompliance());
					b &= contentDescription == null || contentDescription.equalsIgnoreCase(ifc.getContentDescription());
					if (b) {
						list.add(ds);
					}
				}
			}
			return list;
		}
	}

	private boolean updateInterfaceField(final String dsId, final String ifaceId, final Function<IfaceDesc, IfaceDesc> func)
			throws DatasourceManagerServiceException {
		try {
			final DnetDataStructure dds = informationService.getDsByCode(dsId, DatasourceConstants.DATASOURCE_TYPE);
			final Datasource ds = dds.getContentAsObject(Datasource.class);

			for (IfaceDesc ifc : ds.getInterfaces()) {
				if (ifc.getId().equals(ifaceId)) {
					dds.setContentFromObject(func.apply(ifc));
					informationService.registerDs(dds);
					return true;
				}
			}

			return false;
		} catch (InformationServiceException e) {
			log.info(e);
			throw new DatasourceManagerServiceException(e);
		}
	}

	@Override
	public boolean updateLevelOfCompliance(final String dsId, final String ifaceId, final String level) throws DatasourceManagerServiceException {
		return updateInterfaceField(dsId, ifaceId, new Function<IfaceDesc, IfaceDesc>() {

			@Override
			public IfaceDesc apply(final IfaceDesc desc) {
				desc.setCompliance(level);
				return desc;
			}
		});
	}

	@Override
	public boolean overrideCompliance(final String dsId, final String ifaceId, final String level) throws DatasourceManagerServiceException {
		// TODO Auto-generated method stub
		return false;
	}

	@Override
	public boolean updateBaseUrl(final String dsId, final String ifaceId, final String baseUrl) throws DatasourceManagerServiceException {
		return updateInterfaceField(dsId, ifaceId, new Function<IfaceDesc, IfaceDesc>() {

			@Override
			public IfaceDesc apply(final IfaceDesc desc) {
				desc.setBaseUrl(baseUrl);
				return desc;
			}
		});
	}

	@Override
	public boolean updateActivationStatus(final String dsId, final String ifaceId, final boolean active) throws DatasourceManagerServiceException {
		return updateInterfaceField(dsId, ifaceId, new Function<IfaceDesc, IfaceDesc>() {

			@Override
			public IfaceDesc apply(final IfaceDesc desc) {
				desc.setActive(active);
				return desc;
			}
		});
	}

	@Override
	public boolean updateContentDescription(final String dsId, final String ifaceId, final String contentDescription) throws DatasourceManagerServiceException {
		return updateInterfaceField(dsId, ifaceId, new Function<IfaceDesc, IfaceDesc>() {

			@Override
			public IfaceDesc apply(final IfaceDesc desc) {
				desc.setContentDescription(contentDescription);
				return desc;
			}
		});
	}

	@Override
	public boolean setIisProcessingWorkflow(final String dsId, final String ifaceId, final String wf) throws DatasourceManagerServiceException {
		throw new DatasourceManagerServiceException("NOT IMPLEMENTED");
	}

	@Override
	public boolean updateExtraField(final String dsId, final String ifaceId, final String field, final String value, final boolean preserveOriginal)
			throws DatasourceManagerServiceException {
		return updateInterfaceField(dsId, ifaceId, new Function<IfaceDesc, IfaceDesc>() {

			@Override
			public IfaceDesc apply(final IfaceDesc desc) {
				desc.getExtraFields().put(field, value);
				return desc;
			}
		});
	}

	@Override
	public boolean updateAccessParam(final String dsId, final String ifaceId, final String field, final String value, final boolean preserveOriginal)
			throws DatasourceManagerServiceException {

		return updateInterfaceField(dsId, ifaceId, new Function<IfaceDesc, IfaceDesc>() {

			@Override
			public IfaceDesc apply(final IfaceDesc desc) {
				desc.getAccessParams().put(field, value);
				return desc;
			}
		});
	}

	@Override
	public boolean deleteAccessParamOrExtraField(final String dsId, final String ifaceId, final String field) throws DatasourceManagerServiceException {
		return updateInterfaceField(dsId, ifaceId, new Function<IfaceDesc, IfaceDesc>() {

			@Override
			public IfaceDesc apply(final IfaceDesc desc) {
				desc.getAccessParams().remove(field);
				desc.getExtraFields().remove(field);
				return desc;
			}
		});
	}

	@Override
	public boolean addInterface(final String dsId, final IfaceDesc iface) throws DatasourceManagerServiceException {
		try {
			final DnetDataStructure dds = informationService.getDsByCode(dsId, DatasourceConstants.DATASOURCE_TYPE);
			final Datasource ds = dds.getContentAsObject(Datasource.class);

			for (int i = 0; i < ds.getInterfaces().size(); i++) {
				if (ds.getInterfaces().get(0).getId().equals(iface.getId())) {
					ds.getInterfaces().add(i, iface);
					informationService.registerDs(dds);
					return true;
				}
			}
			ds.getInterfaces().add(iface);
			informationService.registerDs(dds);

			return true;
		} catch (InformationServiceException e) {
			log.info(e);
			throw new DatasourceManagerServiceException(e);
		}
	}

	@Override
	public boolean deleteInterface(final String dsId, final String ifaceId) throws DatasourceManagerServiceException {
		try {
			final DnetDataStructure dds = informationService.getDsByCode(dsId, DatasourceConstants.DATASOURCE_TYPE);
			final Datasource ds = dds.getContentAsObject(Datasource.class);

			for (int i = 0; i < ds.getInterfaces().size(); i++) {
				if (ds.getInterfaces().get(0).getId().equals(ifaceId)) {
					ds.getInterfaces().remove(i);
					informationService.registerDs(dds);
					return true;
				}
			}
			return false;
		} catch (InformationServiceException e) {
			log.info(e);
			throw new DatasourceManagerServiceException(e);
		}
	}

	@Override
	public boolean updateSQL(final String dsId, final String sql, final boolean delete) throws DatasourceManagerServiceException {
		throw new DatasourceManagerServiceException("NOT IMPLEMENTED");
	}

	@Override
	public Date findNextScheduledExecution(final String dsId, final String ifaceId) throws DatasourceManagerServiceException {
		// final String xquery = "/*[.//DATAPROVIDER/@interface='" + ifaceId + "' and .//SCHEDULING/@enabled='true']//CRON/text()";
		// TODO
		try {
			final String cronExpression = "";
			final CronExpression cron = new CronExpression(cronExpression);
			return cron.getNextValidTimeAfter(new Date());
		} catch (ParseException e) {
			log.error("Error parsing cron expression", e);
			throw new DatasourceManagerServiceException("Error parsing cron expression", e);
		}
	}

	@Override
	public boolean bulkUpdateApiExtraFields(final String dsId, final String ifaceId, final Map<String, String> fields) throws DatasourceManagerServiceException {
		return updateInterfaceField(dsId, ifaceId, new Function<IfaceDesc, IfaceDesc>() {

			@Override
			public IfaceDesc apply(final IfaceDesc desc) {
				desc.setExtraFields(fields);
				return desc;
			}
		});
	}

	@Override
	public boolean bulkUpdateApiAccessParams(final String dsId, final String ifaceId, final Map<String, String> params)
			throws DatasourceManagerServiceException {

		return updateInterfaceField(dsId, ifaceId, new Function<IfaceDesc, IfaceDesc>() {

			@Override
			public IfaceDesc apply(final IfaceDesc desc) {
				desc.setAccessParams(params);
				return desc;
			}
		});
	}

	@Override
	public List<BrowsableField> listBrowsableFields() throws DatasourceManagerServiceException {
		final List<BrowsableField> list = Lists.newArrayList();

		for (Map.Entry<String, String> e : DnetAnnotationUtils.describeIndexedFields(Datasource.class).entrySet()) {
			list.add(new BrowsableField(e.getKey(), e.getValue()));
		}
		return list;
	}

	@Override
	public List<BrowseTerm> browseField(final String field) throws DatasourceManagerServiceException {
		final List<BrowseTerm> list = Lists.newArrayList();

		// TODO

		return list;
	}

	@Override
	public List<SearchInterfacesEntry> searchInterface(final String field, final String value) throws DatasourceManagerServiceException {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public List<RepositoryMapEntry> getRepositoryMap() throws DatasourceManagerServiceException {
		final List<RepositoryMapEntry> list = Lists.newArrayList();

		for (Datasource ds : listAllDatasources()) {
			final Double lat = ds.getLatitude();
			final Double lng = ds.getLatitude();

			if (lat != null && lng != null && !(lat == 0 && lng == 0)) {
				list.add(new RepositoryMapEntry(ds.getId(), ds.getOfficialName(), lat.floatValue(), lng.floatValue()));
			}
		}

		return list;
	}

	@Override
	public List<SimpleDatasourceDesc> simpleListDatasourcesByType(final String type) throws DatasourceManagerServiceException {
		final List<SimpleDatasourceDesc> list = Lists.newArrayList();
		try {
			for (DnetDataStructure res : informationService.listDatastructures(DatasourceConstants.DATASOURCE_TYPE)) {
				final Datasource ds = res.getContentAsObject(Datasource.class);

				boolean b = false;
				for (IfaceDesc ifc : ds.getInterfaces()) {
					if (ifc.getTypology().equalsIgnoreCase(type)) {
						b = true;
					}
				}

				if (b) {
					final SimpleDatasourceDesc s = new SimpleDatasourceDesc();
					s.setId(ds.getId());
					s.setOrigId(ds.getId());
					s.setName(ds.getOfficialName());
					s.setTypology(type);
					s.setValid(res.isValid());
					for (IfaceDesc ifc : ds.getInterfaces()) {
						s.getApis().add(ifc.getId());
					}
					list.add(s);
				}
			}

			return list;
		} catch (InformationServiceException e) {
			log.info(e);
			throw new DatasourceManagerServiceException(e);
		}
	}
}
