package com.libreecm.opencmis.server;

import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import org.apache.chemistry.opencmis.commons.data.ContentStream;
import org.apache.chemistry.opencmis.commons.data.ExtensionsData;
import org.apache.chemistry.opencmis.commons.data.ObjectData;
import org.apache.chemistry.opencmis.commons.data.ObjectInFolderList;
import org.apache.chemistry.opencmis.commons.data.ObjectParentData;
import org.apache.chemistry.opencmis.commons.data.RepositoryInfo;
import org.apache.chemistry.opencmis.commons.definitions.TypeDefinition;
import org.apache.chemistry.opencmis.commons.definitions.TypeDefinitionList;
import org.apache.chemistry.opencmis.commons.enums.IncludeRelationships;
import org.apache.chemistry.opencmis.commons.impl.MimeTypes;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.ContentStreamImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PartialContentStreamImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.TypeDefinitionListImpl;
import org.apache.chemistry.opencmis.commons.impl.server.AbstractCmisService;
import org.apache.chemistry.opencmis.commons.server.CallContext;
import org.apache.chemistry.opencmis.server.support.wrapper.CallContextAwareCmisService;
import org.springframework.core.io.ByteArrayResource;

import com.libreecm.commonAPI.auxiliars.exceptions.ErrorReadingStreamException;
import com.libreecm.commonAPI.auxiliars.exceptions.NotDocumentException;
import com.libreecm.commonAPI.configuration.ComponentConfig;
import com.libreecm.commonAPI.model.CmisExtensionElement;
import com.libreecm.commonAPI.model.LibreECMObject;
import com.libreecm.commonAPI.model.Repository;
import com.libreecm.commonAPI.model.enums.BaseTypeId;
import com.libreecm.commonAPI.model.objects.Document;
import com.libreecm.commonAPI.model.objects.LibreECMDataObject;
import com.libreecm.commonAPI.model.objects.ObjectType;
import com.libreecm.commonAPI.model.objects.Uri;
import com.libreecm.opencmis.auxiliars.Mapper;
import com.libreecm.opencmis.consumers.ObjectConsumer;
import com.libreecm.opencmis.consumers.RepositoryConsumer;

/**
 * CMIS Service Implementation.
 */
public class LibreECMCmisService extends AbstractCmisService implements CallContextAwareCmisService {

	private CallContext context;

	private RepositoryConsumer repositoryConsumer = new RepositoryConsumer();
	private ObjectConsumer objectConsumer = new ObjectConsumer();

	private HashMap<String, Repository> repositoriesCache = new HashMap<String, Repository>();

	// --- Call Context ---

	/**
	 * Sets the call context.
	 * 
	 * This method should only be called by the service factory.
	 */
	public void setCallContext(CallContext context) {
		this.context = context;
	}

	/**
	 * Gets the call context.
	 */
	public CallContext getCallContext() {
		return context;
	}

	// --- CMIS Operations ---

	public RepositoryInfo getRepositoryInfo(CallContext context, String repositoryId, ExtensionsData extension) {
		com.libreecm.commonAPI.model.Repository repository = null;
		if (repositoriesCache.containsKey(repositoryId))
			repository = repositoriesCache.get(repositoryId);
		else {
			repository = repositoryConsumer.getRepositoryById(repositoryId,
					ComponentConfig.getConfig().getMainRepositoryByIdUrl());
			repositoriesCache.put(repository.getId(), repository);
		}
		RepositoryInfo result = Mapper.map(repository);

		return result;
	}

	@Override
	public List<RepositoryInfo> getRepositoryInfos(ExtensionsData extension) {
		List<RepositoryInfo> reposInfo = new ArrayList<RepositoryInfo>();
		List<Repository> repositories = repositoryConsumer
				.getRepositories(ComponentConfig.getConfig().getMainRepositoriesUrl());
		if (repositories != null) {
			for (Repository repository : repositories) {
				repositoriesCache.put(repository.getId(), repository);
				reposInfo.add(Mapper.map(repository));
			}
		}
		return reposInfo;
	}

	@Override
	/**
	 * getTypeChildren returns all child types of a given type. The second
	 * parameter defines that you want the type definitions without all the
	 * property definitions. If the first parameter, the type, is set to null ,
	 * the repository returns all supported base types.
	 */
	public TypeDefinitionList getTypeChildren(String repositoryId, String typeId, Boolean includePropertyDefinitions,
			BigInteger maxItems, BigInteger skipCount, ExtensionsData extension) {

		Repository repository = null;
		if (repositoriesCache.containsKey(repositoryId))
			repository = repositoriesCache.get(repositoryId);
		else {
			repository = repositoryConsumer.getRepositoryById(repositoryId,
					ComponentConfig.getConfig().getMainRepositoryByIdUrl());
			repositoriesCache.put(repositoryId, repository);
		}
		TypeDefinitionListImpl types = new TypeDefinitionListImpl();
		com.libreecm.commonAPI.model.objects.ExtensionDataImpl extensionList = new com.libreecm.commonAPI.model.objects.ExtensionDataImpl();
		if (extension != null) {
			extensionList = Mapper.mapCmisExtensions(extension.getExtensions());
		}
		for (String id : repository.getCustomizedTypes()) {
			types.getList().add(Mapper.map(objectConsumer.getObjectType(id, repository.getName(),
					ComponentConfig.getConfig().getMainObjectTypeUrl())));
		}

		return types;
	}

	@Override
	public TypeDefinition getTypeDefinition(String repositoryId, String typeId, ExtensionsData extension) {
		com.libreecm.commonAPI.model.Repository repository = null;
		if (repositoriesCache.containsKey(repositoryId))
			repository = repositoriesCache.get(repositoryId);
		else {
			repository = repositoryConsumer.getRepositoryById(repositoryId,
					ComponentConfig.getConfig().getMainRepositoryByIdUrl());
			repositoriesCache.put(repositoryId, repository);
		}
		ObjectType type = objectConsumer.getObjectType(typeId, repository.getName(),
				ComponentConfig.getConfig().getMainObjectTypeUrl());
		if (type.getRepositoryId() != repositoryId)
			type.setRepositoryId(repositoryId);

		TypeDefinition typeDefinition = Mapper.map(type);

		return typeDefinition;
	}

	@Override
	public ObjectInFolderList getChildren(String repositoryId, String folderId, String filter, String orderBy,
			Boolean includeAllowableActions, IncludeRelationships includeRelationships, String renditionFilter,
			Boolean includePathSegment, BigInteger maxItems, BigInteger skipCount, ExtensionsData extension) {
		// com.libreecm.commonAPI.model.objects.ExtensionDataImpl extensionList
		// = new com.libreecm.commonAPI.model.objects.ExtensionDataImpl();
		// if (extension != null){
		// extensionList = Mapper.mapCmisExtensions(extension.getExtensions());
		// }
		LibreECMDataObject parentFolder = objectConsumer.getObject(folderId, filter, includeAllowableActions,
				com.libreecm.commonAPI.model.enums.IncludeRelationships.fromValue(includeRelationships.value()),
				renditionFilter, false, false, // extensionList,
				ComponentConfig.getConfig().getMainObjectUrl());
		List<LibreECMDataObject> children = objectConsumer.getChildrenObjects(parentFolder.getId(), filter, orderBy,
				includeAllowableActions,
				com.libreecm.commonAPI.model.enums.IncludeRelationships.fromValue(includeRelationships.value()),
				renditionFilter, includePathSegment, maxItems, skipCount, // extensionList,
				ComponentConfig.getConfig().getMainObjectsInFolderUrl());

		return Mapper.map(children);
	}

	@Override
	public List<ObjectParentData> getObjectParents(String repositoryId, String objectId, String filter,
			Boolean includeAllowableActions, IncludeRelationships includeRelationships, String renditionFilter,
			Boolean includeRelativePathSegment, ExtensionsData extension) {

		// com.libreecm.commonAPI.model.objects.ExtensionDataImpl extensionList
		// = new com.libreecm.commonAPI.model.objects.ExtensionDataImpl();
		// if (extension != null){
		// extensionList = Mapper.mapCmisExtensions(extension.getExtensions());
		// }
		LibreECMDataObject object = objectConsumer.getObject(objectId, filter, includeAllowableActions,
				com.libreecm.commonAPI.model.enums.IncludeRelationships.fromValue(includeRelationships.value()),
				renditionFilter, false, false, // extensionList,
				ComponentConfig.getConfig().getMainObjectUrl());
		List<LibreECMDataObject> parents = new ArrayList<>();
		for (String parent : object.getFolders()) {
			LibreECMDataObject parentObject = objectConsumer.getObject(parent, filter, false,
					com.libreecm.commonAPI.model.enums.IncludeRelationships.fromValue(includeRelationships.value()), "",
					false, false, // extensionList,
					ComponentConfig.getConfig().getMainObjectUrl());
			parents.add(parentObject);
		}
		return Mapper.mapObjectParents(object, parents);
	}

	@Override
	public ObjectData getObject(String repositoryId, String objectId, String filter, Boolean includeAllowableActions,
			IncludeRelationships includeRelationships, String renditionFilter, Boolean includePolicyIds,
			Boolean includeAcl, ExtensionsData extension) {
		// com.libreecm.commonAPI.model.objects.ExtensionDataImpl extensionList
		// = new com.libreecm.commonAPI.model.objects.ExtensionDataImpl();
		// if (extension != null){
		// extensionList = Mapper.mapCmisExtensions(extension.getExtensions());
		// }
		LibreECMDataObject object = objectConsumer.getObject(objectId, filter, includeAllowableActions,
				com.libreecm.commonAPI.model.enums.IncludeRelationships.fromValue(includeRelationships.value()),
				renditionFilter, includePolicyIds, includeAcl, // extensionList,
				ComponentConfig.getConfig().getMainObjectUrl());
		ObjectData result = Mapper.map(object);
		return result;
	}

	@Override
	public ContentStream getContentStream(String repositoryId, String objectId, String streamId, BigInteger offset,
			BigInteger length, ExtensionsData extension) {

		LibreECMDataObject doc = objectConsumer.getObject(objectId, null, false,
				com.libreecm.commonAPI.model.enums.IncludeRelationships.NONE, null, false, false,
				ComponentConfig.getConfig().getMainObjectUrl());

		if (!doc.getBaseTypeId().equals(BaseTypeId.CMIS_DOCUMENT))
			throw new NotDocumentException(objectId);
		
		ByteArrayResource resource = null;
		if (offset!=null && length!=null)
			resource = objectConsumer.doDownload(objectId, offset.intValueExact(), length.intValueExact(),
				ComponentConfig.getConfig().getMainDownloadObjectUrl());
		else {
			/*BigInteger size=((Document)doc).getContentStreamLength();
			//If object is bigger than 4mb take it in pieces
			if (size.compareTo(BigInteger.valueOf(4000))==1){
				BigInteger count=BigInteger.valueOf(0);
				while (count.compareTo(size)!=1){
					//Read 4.000 bytes or the rest
					resource = objectConsumer.doDownload(objectId, count.intValueExact(),Math.min(4000,size.subtract(count).intValueExact()),
					ComponentConfig.getConfig().getMainDownloadObjectUrl());
					count=count.add(BigInteger.valueOf(Math.min(4000,size.subtract(count).intValueExact())));
				}
			} else*/
				resource = objectConsumer.doDownload(objectId, 0, ((Document)doc).getContentStreamLength().intValueExact(),
						ComponentConfig.getConfig().getMainDownloadObjectUrl());
		}
		// compile data
		ContentStreamImpl result;
		if ((offset != null && offset.longValue() > 0) || length != null) {
			result = new PartialContentStreamImpl();
		} else {
			result = new ContentStreamImpl();
		}
		try {
			result.setFileName(((com.libreecm.commonAPI.model.objects.Document) doc).getContentStreamFileName());
			result.setLength(((com.libreecm.commonAPI.model.objects.Document) doc).getContentStreamLength());
			result.setMimeType(((com.libreecm.commonAPI.model.objects.Document) doc).getContentStreamMimeType());
			result.setStream(resource.getInputStream());
		} catch (IOException e) {
			throw new ErrorReadingStreamException(objectId);
		}
		return result;
	}

}
