/**
 *  '$RCSfile$'
 *  Copyright: 2000-2011 Regents of the University of California and the
 *              National Center for Ecological Analysis and Synthesis
 *
 *   '$Author$'
 *     '$Date$'
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package edu.ucsb.nceas.metacat.dataone.v1;

import java.io.IOException;
import java.io.InputStream;
import java.sql.SQLException;
import java.util.Date;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.io.IOUtils;
import org.apache.log4j.Logger;
import org.dataone.service.exceptions.IdentifierNotUnique;
import org.dataone.service.exceptions.InsufficientResources;
import org.dataone.service.exceptions.InvalidRequest;
import org.dataone.service.exceptions.InvalidSystemMetadata;
import org.dataone.service.exceptions.InvalidToken;
import org.dataone.service.exceptions.NotAuthorized;
import org.dataone.service.exceptions.NotFound;
import org.dataone.service.exceptions.NotImplemented;
import org.dataone.service.exceptions.ServiceFailure;
import org.dataone.service.exceptions.SynchronizationFailed;
import org.dataone.service.exceptions.UnsupportedType;
import org.dataone.service.mn.tier1.v1.MNCore;
import org.dataone.service.mn.tier1.v1.MNRead;
import org.dataone.service.mn.tier2.v1.MNAuthorization;
import org.dataone.service.mn.tier3.v1.MNStorage;
import org.dataone.service.mn.tier4.v1.MNReplication;
import org.dataone.service.mn.v1.MNQuery;
import org.dataone.service.types.v1.Checksum;
import org.dataone.service.types.v1.DescribeResponse;
import org.dataone.service.types.v1.Event;
import org.dataone.service.types.v1.Identifier;
import org.dataone.service.types.v1.Log;
import org.dataone.service.types.v1.Node;
import org.dataone.service.types.v1.NodeReference;
import org.dataone.service.types.v1.ObjectFormatIdentifier;
import org.dataone.service.types.v1.ObjectList;
import org.dataone.service.types.v1.Permission;
import org.dataone.service.types.v1.Session;
import org.dataone.service.types.v1.SystemMetadata;
import org.dataone.service.types.v1_1.QueryEngineDescription;
import org.dataone.service.types.v1_1.QueryEngineList;
import org.dataone.service.types.v2.TypeFactory;

import edu.ucsb.nceas.metacat.IdentifierManager;
import edu.ucsb.nceas.metacat.dataone.convert.LogV2toV1Converter;

/**
 * Represents Metacat's implementation of the DataONE Member Node 
 * service API, v1. Methods typically pass through to the current 
 * version implementation performing type conversion as needed.
 * 
 */
public class MNodeService 
    implements MNAuthorization, MNCore, MNRead, MNReplication, MNStorage, MNQuery {

	/**
	 * current version implementation
	 */
	edu.ucsb.nceas.metacat.dataone.MNodeService impl = null;
	
	/* the logger instance */
    private Logger logMetacat = null;

    /**
     * Singleton accessor to get an instance of MNodeService.
     * 
     * @return instance - the instance of MNodeService
     */
    public static MNodeService getInstance(HttpServletRequest request) {
        return new MNodeService(request);
    }

    /**
     * Constructor, private for singleton access
     */
    private MNodeService(HttpServletRequest request) {
        logMetacat = Logger.getLogger(MNodeService.class);
        impl = edu.ucsb.nceas.metacat.dataone.MNodeService.getInstance(request);
    }
    
    public void setSession(Session session) {
    	impl.setSession(session);
    }
    
    public boolean isAdminAuthorized(Session session) throws ServiceFailure, InvalidToken, NotAuthorized, NotImplemented {
    	return impl.isAdminAuthorized(session);
    }

	@Override
	public QueryEngineDescription getQueryEngineDescription(String engine)
			throws InvalidToken, ServiceFailure, NotAuthorized, NotImplemented,
			NotFound {
		return impl.getQueryEngineDescription(null, engine);
	}

	@Override
	public QueryEngineList listQueryEngines() throws InvalidToken,
			ServiceFailure, NotAuthorized, NotImplemented {
		return impl.listQueryEngines(null);
	}

	@Override
	public InputStream query(String engine, String query) throws InvalidToken,
			ServiceFailure, NotAuthorized, InvalidRequest, NotImplemented,
			NotFound {
		return impl.query(null, engine, query);
	}

	@Override
	public Identifier archive(Identifier pid) throws InvalidToken,
			ServiceFailure, NotAuthorized, NotFound, NotImplemented {
	    String serviceFailure = "2912";
        String notFound = "2911";
        impl.checkV1SystemMetaPidExist(pid, serviceFailure, "The object specified by "+pid.getValue()+" couldn't be identified if it exists",  notFound, 
                "The object specified by "+pid.getValue()+" does not exist at this node.");
		return impl.archive(null, pid);
	}

	@Override
	@Deprecated
	public Identifier archive(Session session, Identifier pid)
			throws InvalidToken, ServiceFailure, NotAuthorized, NotFound,
			NotImplemented {
	    String serviceFailure = "2912";
        String notFound = "2911";
        impl.checkV1SystemMetaPidExist(pid, serviceFailure, "The object specified by "+pid.getValue()+" couldn't be identified if it exists",  notFound, 
                "The object specified by "+pid.getValue()+" does not exist at this node.");
		return impl.archive(session, pid);
	}

	@Override
	public Identifier create(Identifier pid, InputStream object,
			SystemMetadata sysmeta) throws IdentifierNotUnique,
			InsufficientResources, InvalidRequest, InvalidSystemMetadata,
			InvalidToken, NotAuthorized, NotImplemented, ServiceFailure,
			UnsupportedType {
		return this.create(null, pid, object, sysmeta);
	}

	@Override
	@Deprecated
	public Identifier create(Session session, Identifier pid, InputStream object,
			SystemMetadata sysmeta) throws IdentifierNotUnique,
			InsufficientResources, InvalidRequest, InvalidSystemMetadata,
			InvalidToken, NotAuthorized, NotImplemented, ServiceFailure,
			UnsupportedType {
		//convert sysmeta to newer version
		org.dataone.service.types.v2.SystemMetadata v2Sysmeta = null;
		try {
			v2Sysmeta = TypeFactory.convertTypeFromType(sysmeta, org.dataone.service.types.v2.SystemMetadata.class);
		} catch (Exception e) {
		    IOUtils.closeQuietly(object);
			// report as service failure
			ServiceFailure sf = new ServiceFailure("1190", e.getMessage());
			sf.initCause(e);
			throw sf;
		}
		
		return impl.create(session, pid, object, v2Sysmeta);
	}

	@Override
	public Identifier delete(Identifier pid) throws InvalidToken,
			ServiceFailure, NotAuthorized, NotFound, NotImplemented {
	    String serviceFailure = "2902";
        String notFound = "2901";
        impl.checkV1SystemMetaPidExist(pid, serviceFailure, "The object specified by "+pid.getValue()+" couldn't be identified if it exists",  notFound, 
                "The object specified by "+pid.getValue()+" does not exist at this node.");
		return impl.delete(null, pid);
	}

	@Override
	@Deprecated
	public Identifier delete(Session session, Identifier pid)
			throws InvalidToken, ServiceFailure, NotAuthorized, NotFound,
			NotImplemented {
	    String serviceFailure = "2902";
        String notFound = "2901";
        impl.checkV1SystemMetaPidExist(pid, serviceFailure, "The object specified by "+pid.getValue()+" couldn't be identified if it exists",  notFound, 
                "The object specified by "+pid.getValue()+" does not exist at this node.");
		return impl.delete(session, pid);
	}

	@Override
	public Identifier generateIdentifier(String scheme, String fragment)
			throws InvalidToken, ServiceFailure, NotAuthorized, NotImplemented,
			InvalidRequest {
		return impl.generateIdentifier(null, scheme, fragment);
	}

	@Override
	@Deprecated
	public Identifier generateIdentifier(Session session, String scheme, String fragment)
			throws InvalidToken, ServiceFailure, NotAuthorized, NotImplemented,
			InvalidRequest {
		return impl.generateIdentifier(session, scheme, fragment);
	}

	@Override
	public Identifier update(Identifier pid, InputStream object,
			Identifier newPid, SystemMetadata sysmeta) throws IdentifierNotUnique,
			InsufficientResources, InvalidRequest, InvalidSystemMetadata,
			InvalidToken, NotAuthorized, NotImplemented, ServiceFailure,
			UnsupportedType, NotFound {
		return this.update(null, pid, object, newPid, sysmeta);
	}

	@Override
	@Deprecated
	public Identifier update(Session session, Identifier pid, InputStream object,
			Identifier newPid, SystemMetadata sysmeta) throws IdentifierNotUnique,
			InsufficientResources, InvalidRequest, InvalidSystemMetadata,
			InvalidToken, NotAuthorized, NotImplemented, ServiceFailure,
			UnsupportedType, NotFound {
	    //check if the pid exists and not a sid
	    String serviceFailure = "1310";
        String notFound = "1280";
        try {
            impl.checkV1SystemMetaPidExist(pid, serviceFailure, "The object specified by "+pid.getValue()+" couldn't be identified if it exists",  notFound, 
                "The object specified by "+pid.getValue()+" does not exist at this node.");
        } catch (Exception e) {
            IOUtils.closeQuietly(object);
            throw e;
        }
		//convert sysmeta to newer version
		org.dataone.service.types.v2.SystemMetadata v2Sysmeta = null;
		try {
			v2Sysmeta = TypeFactory.convertTypeFromType(sysmeta, org.dataone.service.types.v2.SystemMetadata.class);
		} catch (Exception e) {
		    IOUtils.closeQuietly(object);
			// report as service failure
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
			sf.initCause(e);
			throw sf;
		}
		return impl.update(session, pid, object, newPid, v2Sysmeta);
	}

	@Override
	public boolean replicate(SystemMetadata sysmeta, NodeReference sourceNode)
			throws NotImplemented, ServiceFailure, NotAuthorized,
			InvalidRequest, InvalidToken, InsufficientResources,
			UnsupportedType {
		return this.replicate(null, sysmeta, sourceNode);
	}

	@Override
	@Deprecated
	public boolean replicate(Session session, SystemMetadata sysmeta,
			NodeReference sourceNode) throws NotImplemented, ServiceFailure,
			NotAuthorized, InvalidRequest, InvalidToken, InsufficientResources,
			UnsupportedType {
		//convert sysmeta to newer version
		org.dataone.service.types.v2.SystemMetadata v2Sysmeta = null;
		try {
			v2Sysmeta = TypeFactory.convertTypeFromType(sysmeta, org.dataone.service.types.v2.SystemMetadata.class);
		} catch (Exception e) {
			// report as service failure
			ServiceFailure sf = new ServiceFailure("1030", e.getMessage());
			sf.initCause(e);
			throw sf;
		}
		return impl.replicate(session, v2Sysmeta, sourceNode);
	}

	@Override
	public DescribeResponse describe(Identifier pid) throws InvalidToken,
			NotAuthorized, NotImplemented, ServiceFailure, NotFound {
	    String serviceFailure = "1030";
        String notFound = "1020";
        impl.checkV1SystemMetaPidExist(pid, serviceFailure, "The system metadata for given PID "+pid.getValue()+" couldn't be identified if it exists",  notFound, 
                "No system metadata could be found for given PID: "+pid.getValue());
		return impl.describe(null, pid);
	}

	@Override
	@Deprecated
	public DescribeResponse describe(Session session, Identifier pid)
			throws InvalidToken, NotAuthorized, NotImplemented, ServiceFailure,
			NotFound {
	    String serviceFailure = "1030";
        String notFound = "1020";
        impl.checkV1SystemMetaPidExist(pid, serviceFailure, "The system metadata for given PID "+pid.getValue()+" couldn't be identified if it exists",  notFound, 
                "No system metadata could be found for given PID: "+pid.getValue());
		return impl.describe(session, pid);
	}

	@Override
	public InputStream get(Identifier pid) throws InvalidToken, NotAuthorized,
			NotImplemented, ServiceFailure, NotFound, InsufficientResources {
	    String serviceFailure = "1030";
        String notFound = "1020";
        impl.checkV1SystemMetaPidExist(pid, serviceFailure, "The object specified by "+pid.getValue()+" couldn't be identified if it exists",  notFound, 
                "The object specified by "+pid.getValue()+" does not exist at this node.");
		return impl.get(null, pid);
	}

	@Override
	@Deprecated
	public InputStream get(Session session, Identifier pid) throws InvalidToken,
			NotAuthorized, NotImplemented, ServiceFailure, NotFound,
			InsufficientResources {
	    String serviceFailure = "1030";
        String notFound = "1020";
        impl.checkV1SystemMetaPidExist(pid, serviceFailure, "The object specified by "+pid.getValue()+" couldn't be identified if it exists",  notFound, 
                "The object specified by "+pid.getValue()+" does not exist at this node.");
		return impl.get(session, pid);
	}

	@Override
	public Checksum getChecksum(Identifier pid, String algorithm)
			throws InvalidRequest, InvalidToken, NotAuthorized, NotImplemented,
			ServiceFailure, NotFound {
		return impl.getChecksum(null, pid, algorithm);
	}

	@Override
	@Deprecated
	public Checksum getChecksum(Session session, Identifier pid, String algorithm)
			throws InvalidRequest, InvalidToken, NotAuthorized, NotImplemented,
			ServiceFailure, NotFound {
		return impl.getChecksum(session, pid, algorithm);
	}

	@Override
	public InputStream getReplica(Identifier pid) throws InvalidToken,
			NotAuthorized, NotImplemented, ServiceFailure, NotFound,
			InsufficientResources {
		return impl.getReplica(null, pid);
	}

	@Override
	@Deprecated
	public InputStream getReplica(Session session, Identifier pid)
			throws InvalidToken, NotAuthorized, NotImplemented, ServiceFailure,
			NotFound, InsufficientResources {
		return impl.getReplica(session, pid);
	}

	@Override
	public SystemMetadata getSystemMetadata(Identifier pid)
			throws InvalidToken, NotAuthorized, NotImplemented, ServiceFailure,
			NotFound {
		
		return this.getSystemMetadata(null, pid);
		
	}

	@Override
	@Deprecated
	public SystemMetadata getSystemMetadata(Session session, Identifier pid)
			throws InvalidToken, NotAuthorized, NotImplemented, ServiceFailure,
			NotFound {
	    String serviceFailure = "1090";
        String notFound = "1060";
        impl.checkV1SystemMetaPidExist(pid, serviceFailure, "The system metadata for given PID "+pid.getValue()+" couldn't be identified if it exists",  notFound, 
                "No system metadata could be found for given PID: "+pid.getValue());
		org.dataone.service.types.v2.SystemMetadata sysMeta = impl.getSystemMetadata(session, pid);
		SystemMetadata retSysMeta = null;
		try {
			retSysMeta = TypeFactory.convertTypeFromType(sysMeta, SystemMetadata.class);
		} catch (Exception e) {
			// report as service failure
			ServiceFailure sf = new ServiceFailure("4801", e.getMessage());
			sf.initCause(e);
			throw sf;
		}
		return retSysMeta;
	}

	@Override
	public ObjectList listObjects(Date startTime, Date endTime,
			ObjectFormatIdentifier objectFormatId, Boolean replicaStatus, Integer start,
			Integer count) throws InvalidRequest, InvalidToken, NotAuthorized,
			NotImplemented, ServiceFailure {
		return this.listObjects(null, startTime, endTime, objectFormatId, replicaStatus, start, count);
	}

	@Override
	@Deprecated
	public ObjectList listObjects(Session session, Date startTime, Date endTime,
			ObjectFormatIdentifier objectFormatId, Boolean replicaStatus, Integer start,
			Integer count) throws InvalidRequest, InvalidToken, NotAuthorized,
			NotImplemented, ServiceFailure {
		return impl.listObjects(session, startTime, endTime, objectFormatId, null, replicaStatus, start, count);
	}

	@Override
	public boolean synchronizationFailed(SynchronizationFailed syncFailed)
			throws InvalidToken, NotAuthorized, NotImplemented, ServiceFailure {
		return impl.synchronizationFailed(null, syncFailed);
	}

	@Override
	@Deprecated
	public boolean synchronizationFailed(Session session,
			SynchronizationFailed syncFailed) throws InvalidToken, NotAuthorized,
			NotImplemented, ServiceFailure {
		return impl.synchronizationFailed(session, syncFailed);
	}

	@Override
	public Node getCapabilities() throws NotImplemented, ServiceFailure {
		org.dataone.service.types.v2.Node node = impl.getCapabilities();
		Node retNode = null;
		try {
			retNode = TypeFactory.convertTypeFromType(node, Node.class);
		} catch (Exception e) {
			// report as service failure
			ServiceFailure sf = new ServiceFailure("4801", e.getMessage());
			sf.initCause(e);
			throw sf;
		}
		return retNode;
	}

	@Override
	public Log getLogRecords(Date fromDate, Date toDate, Event event,
			String pidFilter, Integer start, Integer count) throws InvalidRequest, InvalidToken,
			NotAuthorized, NotImplemented, ServiceFailure {
		return this.getLogRecords(null, fromDate, toDate, event, pidFilter, start, count);
	}

	@Override
	@Deprecated
	public Log getLogRecords(Session session, Date fromDate, Date toDate, Event event,
			String pidFilter, Integer start, Integer count) throws InvalidRequest, InvalidToken,
			NotAuthorized, NotImplemented, ServiceFailure {
	    Log retLog = new Log();
	    if(pidFilter != null && !pidFilter.trim().equals("")) {
	        String serviceFailure = "1490";
	        String notFound = "1020";
	        Identifier pid = new Identifier();
	        pid.setValue(pidFilter);
	        try {
	            impl.checkV1SystemMetaPidExist(pid, serviceFailure, "The object for given PID "+pid.getValue()+" couldn't be identified if it exists",  notFound, 
	                    "The given PID: "+pid.getValue()+" doesn't exist in this node");
	        } catch (NotFound e) {
	            //return 0 record since the pid doesn't exist
	            logMetacat.info(e.getMessage());
	            return retLog;
	        }
	        
	    }
	   
	    String eventValue = null;
	    if(event != null) {
	        eventValue = event.xmlValue();
	    }
		org.dataone.service.types.v2.Log log = impl.getLogRecords(session, fromDate, toDate, eventValue, pidFilter, start, count);
		
		try {
			retLog = TypeFactory.convertTypeFromType(log, Log.class);
		    //LogV2toV1Converter converter = new LogV2toV1Converter();
		   // retLog = converter.convert(log);
		} catch (Exception e) {
			// report as service failure
			ServiceFailure sf = new ServiceFailure("1490", e.getMessage());
			sf.initCause(e);
			throw sf;
		}
		return retLog;
	}

	@Override
	public Date ping() throws NotImplemented, ServiceFailure,
			InsufficientResources {
		return impl.ping();
	}

	@Override
	public boolean isAuthorized(Identifier pid, Permission permission)
			throws ServiceFailure, InvalidRequest, InvalidToken, NotFound,
			NotAuthorized, NotImplemented {
	    String serviceFailure = "1760";
        String notFound = "1800";
        impl.checkV1SystemMetaPidExist(pid, serviceFailure, "The object specified by "+pid.getValue()+" couldn't be identified if it exists",  notFound, 
                "The object specified by "+pid.getValue()+" does not exist at this node.");
		return impl.isAuthorized(null, pid, permission);
	}

	@Override
	@Deprecated
	public boolean isAuthorized(Session session, Identifier pid, Permission permission)
			throws ServiceFailure, InvalidRequest, InvalidToken, NotFound,
			NotAuthorized, NotImplemented {
	    String serviceFailure = "1760";
        String notFound = "1800";
        impl.checkV1SystemMetaPidExist(pid, serviceFailure, "The object specified by "+pid.getValue()+" couldn't be identified if it exists",  notFound, 
                "The object specified by "+pid.getValue()+" does not exist at this node.");
		return impl.isAuthorized(session, pid, permission);
	}

	@Override
	public boolean systemMetadataChanged(Identifier pid, long serialVersion, Date dateSysMetaLastModified)
			throws InvalidToken, ServiceFailure, NotAuthorized, NotImplemented,
			InvalidRequest {
	    String serviceFailure = "1333";
        String notFound = "1800";
        try {
            impl.checkV1SystemMetaPidExist(pid, serviceFailure, "The system metadata of the object specified by "+pid.getValue()+" couldn't be identified if it exists",  notFound, 
                    "The system metadata of the object specified by "+pid.getValue()+" does not exist at this node.");
        } catch (NotFound e) {
            throw new ServiceFailure(serviceFailure, e.getMessage());
        }
        boolean needCheckAuthoriativeNode = false; 
		return impl.systemMetadataChanged(needCheckAuthoriativeNode, null, pid, serialVersion, dateSysMetaLastModified);

	}

	@Override
	@Deprecated
	public boolean systemMetadataChanged(Session session, Identifier pid,
			long serialVersion, Date dateSysMetaLastModified) throws InvalidToken, ServiceFailure,
			NotAuthorized, NotImplemented, InvalidRequest {
	    String serviceFailure = "1333";
        String notFound = "1800";
        try {
            impl.checkV1SystemMetaPidExist(pid, serviceFailure, "The system metadata of the object specified by "+pid.getValue()+" couldn't be identified if it exists",  notFound, 
                    "The system metadata of the object specified by "+pid.getValue()+" does not exist at this node.");
        } catch (NotFound e) {
            throw new ServiceFailure(serviceFailure, e.getMessage());
        }
        boolean needCheckAuthoriativeNode = false; 
		return impl.systemMetadataChanged(needCheckAuthoriativeNode,session, pid, serialVersion, dateSysMetaLastModified);
	}
    
	// methods not defined in v1, but implemented in metacat pre-v2 release
	
	public Identifier publish(Session session, Identifier originalIdentifier) 
			throws InvalidToken, ServiceFailure, NotAuthorized, NotImplemented, InvalidRequest, NotFound, IdentifierNotUnique, UnsupportedType, InsufficientResources, InvalidSystemMetadata, IOException {
	    String serviceFailure = "1030";
        String notFound = "1020";
        impl.checkV1SystemMetaPidExist(originalIdentifier, serviceFailure, "The system metadata of the object specified by "+originalIdentifier.getValue()+" couldn't be identified if it exists",  notFound, 
                    "The system metadata of the object specified by "+originalIdentifier.getValue()+" does not exist at this node.");
		return impl.publish(session, originalIdentifier);
		
	}
	
	public InputStream view(Session session, String format, Identifier pid)
			throws InvalidToken, ServiceFailure, NotAuthorized, InvalidRequest,
			NotImplemented, NotFound {
	    String serviceFailure = "2831";
        String notFound = "2835";
        impl.checkV1SystemMetaPidExist(pid, serviceFailure, "The system metadata of the object specified by "+pid.getValue()+" couldn't be identified if it exists",  notFound, 
                    "The system metadata of the object specified by "+pid.getValue()+" does not exist at this node.");
       
		return impl.view(session, format, pid);
	}
	
	public InputStream getPackage(Session session, ObjectFormatIdentifier formatId,
			Identifier pid) throws InvalidToken, ServiceFailure,
			NotAuthorized, InvalidRequest, NotImplemented, NotFound {
	    String serviceFailure = "2871";
        String notFound = "2875";
        impl.checkV1SystemMetaPidExist(pid, serviceFailure, "The system metadata of the object specified by "+pid.getValue()+" couldn't be identified if it exists",  notFound, 
                    "The system metadata of the object specified by "+pid.getValue()+" does not exist at this node.");
        
		return impl.getPackage(session, formatId, pid);
	}
	
    

   
}