/*****************************************************************
 JADE - Java Agent DEvelopment Framework is a framework to develop
 multi-agent systems in compliance with the FIPA specifications.
 Copyright (C) 2000 CSELT S.p.A.
 
 GNU Lesser General Public License
 
 This library is free software; you can redistribute it and/or
 modify it under the terms of the GNU Lesser General Public
 License as published by the Free Software Foundation,
 version 2.1 of the License.
 
 This library 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
 Lesser General Public License for more details.
 
 You should have received a copy of the GNU Lesser General Public
 License along with this library; if not, write to the
 Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 Boston, MA  02111-1307, USA.
 *****************************************************************/
package jade.core.resource;

//#J2ME_EXCLUDE_FILE
//#APIDOC_EXCLUDE_FILE

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;

import jade.core.Agent;
import jade.core.AgentContainer;
import jade.core.BaseService;
import jade.core.HorizontalCommand;
import jade.core.IMTPException;
import jade.core.Node;
import jade.core.NotFoundException;
import jade.core.Profile;
import jade.core.ProfileException;
import jade.core.Service;
import jade.core.ServiceException;
import jade.core.ServiceHelper;
import jade.core.VerticalCommand;
import jade.util.Logger;

/**
 * ResourceManagement service main class
 */
public class ResourceManagementService extends BaseService {
	public static final String NAME = ResourceManagementHelper.SERVICE_NAME;

	private ServiceComponent localSlice;
	private ResourceManagementHelper helper;

	@Override
	public void init(AgentContainer ac, Profile p) throws ProfileException {
		super.init(ac, p);
		
		localSlice = new ServiceComponent();
	}

	@Override
	public void boot(Profile p) throws ServiceException {
		super.boot(p);

		helper = new ResourceManagementHelperImpl();
		SingletonResourceManagementHelper.getInstance().addHelper(helper);
	}

	@Override
	public void shutdown() {
		SingletonResourceManagementHelper.getInstance().removeHelper(helper);
		helper = null;
	}
	
	public String getName() {
		return NAME;
	}

	@Override
	public Class getHorizontalInterface() {
		return ResourceManagementSlice.class;
	}
	
	@Override
	public Service.Slice getLocalSlice() {
		return localSlice;
	}
	
	@Override
	public ServiceHelper getHelper(Agent a) {
		return helper;
	}
	
	public static ResourceManagementHelper getHelper() {
		return SingletonResourceManagementHelper.getInstance().getHelper();
	}
	
	
	/**
	 * Inner class ServiceComponent
	 */
	private class ServiceComponent implements Service.Slice {

		public Node getNode() throws ServiceException {
			try {
				return ResourceManagementService.this.getLocalNode();
			}
			catch(IMTPException imtpe) {
				throw new ServiceException("Error retrieving local node", imtpe);
			}
		}

		public Service getService() {
			return ResourceManagementService.this;
		}

		public VerticalCommand serve(HorizontalCommand cmd) {
			try {
				String cmdName = cmd.getName();
				Object[] params = cmd.getParams();
				
				if (cmdName.equals(ResourceManagementSlice.H_GETRESOURCE)) {
					String name = (String) params[0];
					int fetchMode = (Integer) params[1];
					
					if (myLogger.isLoggable(Logger.FINE)) {
						myLogger.log(Logger.FINE, "Serve getResource "+name+" with fetchMode "+fetchMode);
					}					
					
					byte[] resource = getResource(name, fetchMode);
					cmd.setReturnValue(resource);
				}
			}
			catch (Throwable t) {
				cmd.setReturnValue(t);
			}
			return null;
		}

		private byte[] getResource(String name, int fetchMode) throws IOException, NotFoundException {

			// Search in classpath
			if (fetchMode == ResourceManagementHelper.CLASSPATH_RESOURCES) {
				return getResourceFromClasspath(name);
			}
			
			// Search in file system
			if (fetchMode == ResourceManagementHelper.FILE_SYSTEM_RESOURCES) {
				return getResourceFromFileSystem(name);
			}
			
			// Search in shared resources
			if (fetchMode == ResourceManagementHelper.SHARED_RESOURCES) {
				return getResourceFromShared(name);
			}
			
			// Search in all (shared->classpath->filesystem)
			if (fetchMode == ResourceManagementHelper.ALL_RESOURCES) {
				byte[] res = null; 
				try {
					res = getResourceFromShared(name);
				} catch(NotFoundException nfe1) {
					try {
						res = getResourceFromClasspath(name);
					} catch(NotFoundException nfe2) {
						res = getResourceFromFileSystem(name);
					}
				}
				return res;
			}
			
			// Wrong fetch mode
			myLogger.log(Logger.WARNING, "Received getResource "+name+" with unsupported fetchMode "+fetchMode);
			throw new IllegalArgumentException("getResource "+name+" with unsupported fetchMode "+fetchMode);
		}

		private byte[] getResourceFromShared(String name) throws IOException, NotFoundException {
			// Get shared folder
			String sharedFolder = System.getProperty(ResourceManagementHelper.SHARED_RESOURCES_FOLDER_KEY);
			if (sharedFolder == null) {
				sharedFolder = "";
			} else {
				sharedFolder += File.separator;
			}
			
			return getResourceFromFileSystem(sharedFolder+name);
		}

		private byte[] getResourceFromFileSystem(String name) throws IOException, NotFoundException {
			return getResourceFromFile(new File(name));
		}
		
		private byte[] getResourceFromClasspath(String name) throws IOException, NotFoundException {
			// Get resource URL
			URL resourceUrl = getClass().getClassLoader().getResource(name);
			if (resourceUrl == null) {
				throw new NotFoundException("Resource " + name + " not found in class-path");
			}

			// Get input stream and read the bytes
			URLConnection uc = (URLConnection)resourceUrl.openConnection();
			InputStream is = uc.getInputStream();
			byte[] resource = getResourceFromStream(is);
			
			return resource;
		}

		private byte[] getResourceFromFile(File file) throws IOException, NotFoundException {
			if (!file.exists()) {
				throw new NotFoundException("Resource " + file.getName() + " not found");
			}
			
			return getResourceFromStream(new FileInputStream(file));
		}
		
		private byte[] getResourceFromStream(InputStream is) throws IOException {
	        long length = is.available();
	        byte[] bytes = new byte[(int)length];
	    
	        int offset = 0;
	        int numRead = 0;
	        while (offset < bytes.length
	        		&& (numRead = is.read(bytes, offset, Math.min(bytes.length - offset, 512*1024))) >= 0) { 
	            offset += numRead;
	        }
	    
	        if (offset < bytes.length) { 
	        	throw new IOException("Could not completely read the resource"); 
	        }
	        
	        is.close();
	        return bytes;			
		}
	} // END of inner class ServiceComponent
	
	
	/**
	 * Inner class ResourceManagementHelperImpl
	 */	
	private class ResourceManagementHelperImpl implements ResourceManagementHelper {

		public void init(Agent a) {
		}

		public byte[] getResource(String name) throws ServiceException, NotFoundException {
			return getResource(name, ResourceManagementHelper.ALL_RESOURCES, null);
		}

		public byte[] getResource(String name, int fetchMode) throws ServiceException, NotFoundException {
			return getResource(name, fetchMode, null);
		}

		public byte[] getResource(String name, String containerName) throws ServiceException, NotFoundException {
			return getResource(name, ResourceManagementHelper.ALL_RESOURCES, containerName);
		}

		public byte[] getResource(String name, int fetchMode, String containerName) throws ServiceException, NotFoundException {
			// If not specified send request to Main-Container
			if (containerName == null) {
				containerName = MAIN_SLICE;
			}
			
			// Get slice for specific container
			ResourceManagementSlice slice = (ResourceManagementSlice)getSlice(containerName);
			if (slice == null) {
				throw new ServiceException("getResource called with a wrong container name (" + containerName + ")");
			}
			
			// Get resource
			byte[] resource;
			try {
				try {
					resource = slice.getResource(name, fetchMode);
				} 
				catch (IMTPException imtpe) {
					// Try to get a newer slice and repeat...
					slice = (ResourceManagementSlice) getFreshSlice(containerName);
					resource = slice.getResource(name, fetchMode);
				}
			} catch (NotFoundException nfe) {
				throw nfe;
			} catch (ServiceException se) {
				throw se;
			} catch (Throwable t) {
				throw new ServiceException("Error accessing resource " + name + " from " + slice.getNode().getName() + " with fetch-mode " + fetchMode, t);
			}
			
			return resource;
		}
	} // END of inner class ResourceManagementHelperImpl

}
