/*
 *   Copyright 2014 Brian Rosenberger (Brutex Network)
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 */

package net.brutex.xservices.ws.rs;



import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.nio.file.FileVisitOption;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;

import org.apache.commons.configuration2.PropertiesConfiguration;
import org.apache.commons.configuration2.builder.fluent.Configurations;
import org.apache.commons.configuration2.ex.ConfigurationException;
import org.apache.commons.jcs.JCS;
import org.apache.commons.jcs.access.CacheAccess;
import org.apache.commons.jcs.access.exception.CacheException;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import net.brutex.xservices.cmtypes.ItemType;
import net.brutex.xservices.cmtypes.ItemTypeList;
import net.brutex.xservices.types.FileInfoType;
import net.brutex.xservices.util.FileWalker;



/*
 * The Serena Dimensions CM Java API is required for these imports.
 * The API is not included in this package due to copyright reasons,
 * please get Dimensions CM from Serena Software Inc., Evaluation versions
 * are available from http://www.serena.com
 * 
 * required Jars:
 * serena.darius-14.1.jar
 * serena.dmclient-14.1.jar
 * serena.dmfile-14.1.jar
 * serena.dmnet-14.1.jar
 * serena.dmtpi-14.1.jar
 * 
 */

import com.serena.dmclient.api.BulkOperator;
import com.serena.dmclient.api.DimensionsConnection;
import com.serena.dmclient.api.DimensionsConnectionDetails;
import com.serena.dmclient.api.DimensionsConnectionManager;
import com.serena.dmclient.api.ItemRevision;
import com.serena.dmclient.api.Project;
import com.serena.dmclient.api.RepositoryFolder;
import com.serena.dmclient.api.SystemAttributes;

/**
 * The Class FileInfoImpl.
 * 
 * @author Brian Rosenberger, bru(at)brutex.de
 */
public class DIMCMInfoImpl implements DIMCMInfo {

	Logger logger = LogManager.getLogger();

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * net.brutex.xservices.ws.rs.FileInfo#getFiles(javax.ws.rs.core.HttpHeaders
	 * , java.lang.String, boolean, boolean, int, java.lang.String, int, int)
	 */
	public Response getFiles(HttpHeaders h, UriInfo uriInfo, String projSpec,
			String directory, boolean recursive, boolean withFiles, int level,
			String search, int count, int page, boolean useCache) throws CacheException {

		
		/*
		 * try to hit cache first
		 */
		CacheAccess<Object, Object> cache = JCS.getInstance("DIMCM");
		String cachekey = projSpec + directory + String.valueOf(recursive);
		if(useCache) {
				ItemTypeList cacheresult = (ItemTypeList) cache.get(cachekey);
				if(cacheresult != null) return Response.ok(cacheresult).build();
		}
		
		//Reject when project has not the form "PRODUCT:PROJECT"
		if(! projSpec.contains(":")) return Response.noContent().build();
		
		Project project = getDIMCMConnection().getObjectFactory().getProject(projSpec);
		RepositoryFolder folder = null;
		
		if (directory == null) {
			folder = project.getRootFolder();
		} else {
			while(directory.startsWith("/") || directory.startsWith("\\")) {
				directory = directory.substring(1);
			}
			if(directory.equals("")) {
				folder = project.getRootFolder();
			} else {
				folder = project.findRepositoryFolderByPath(directory);
			}
		}

		
		ItemTypeList resultlist = new ItemTypeList();
		resultlist.list = getItems(folder, recursive);
		if(cache!=null) cache.put(cachekey, resultlist);
		
		//does this help?
		DimensionsConnectionManager.unregisterThreadConnection();
		
		return Response.ok(resultlist).build();

	}

	
	
	
	List<ItemType> getItems(RepositoryFolder f, boolean recursive) {
		DimensionsConnection conn = getDIMCMConnection();
		List<ItemType> result = new ArrayList<>();

		/* get Items from current folder */
		/* latest revision only */
		List<ItemRevision> revisions = f.getLatestItemRevisions();

		int[] attr = { SystemAttributes.FULL_PATH_NAME,
				SystemAttributes.ITEMFILE_DIR,
				SystemAttributes.ITEMFILE_FILENAME,
				SystemAttributes.ITEMFILE_DIR, SystemAttributes.OBJECT_SPEC,
				SystemAttributes.OBJECT_ID, SystemAttributes.OBJECT_SPEC_UID,
				SystemAttributes.OBJECT_UID, SystemAttributes.CREATION_DATE,
				SystemAttributes.CREATION_USER, SystemAttributes.ITEM_FORMAT,
				SystemAttributes.LAST_UPDATED_DATE,
				SystemAttributes.LAST_UPDATED_USER };
		BulkOperator bulk = conn.getObjectFactory().getBulkOperator(revisions);
		bulk.queryAttribute(attr);

		// Copy into JAXB object
		for (ItemRevision r : revisions) {
			ItemType item = new ItemType();
			item.setLongFilename((String) r
					.getAttribute(SystemAttributes.FULL_PATH_NAME));
			item.setDirName((String) r
					.getAttribute(SystemAttributes.ITEMFILE_DIR));
			item.setShortFilename((String) r
					.getAttribute(SystemAttributes.ITEMFILE_FILENAME));
			item.setObject_id((String) r
					.getAttribute(SystemAttributes.OBJECT_ID));
			item.setObject_uid((String.valueOf(r
					.getAttribute(SystemAttributes.OBJECT_UID))));
			item.setObject_spec((String) r
					.getAttribute(SystemAttributes.OBJECT_SPEC));
			item.setObject_spec_uid(String.valueOf(r
					.getAttribute(SystemAttributes.OBJECT_SPEC_UID)));
			item.setObject_spec_uid(String.valueOf(r
					.getAttribute(SystemAttributes.OBJECT_SPEC_UID)));
			item.setCreatedDate(String.valueOf(r
					.getAttribute(SystemAttributes.CREATION_DATE)));
			item.setCreatedUser(String.valueOf(r
					.getAttribute(SystemAttributes.CREATION_USER)));
			item.setItemFormat(String.valueOf(r
					.getAttribute(SystemAttributes.ITEM_FORMAT)));
			item.setUpdatedDate(String.valueOf(r
					.getAttribute(SystemAttributes.LAST_UPDATED_DATE)));
			item.setUpdatedUser(String.valueOf(r
					.getAttribute(SystemAttributes.LAST_UPDATED_USER)));

			try {
				item.setUrl(new URL(getBaseURL()
						+ "?jsp=api&command=openi&object_id="
						+ item.getObject_spec() + "&DB_CONN="
						+ conn.getConnectionDetails().getDbConn() + "&DB_NAME="
						+ conn.getConnectionDetails().getDbName()));
			} catch (MalformedURLException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			result.add(item);
		}
		
		/*
		 * for recursive add other folders
		 */
		if(recursive) {
			List<RepositoryFolder> folders = f.getAllChildFolders();
			for(RepositoryFolder ff : folders) {
				result.addAll(getItems(ff, false));
			}
		}
		
		return result;
	}

	/**
	 * Sets the directory.
	 * 
	 * @param list
	 *            the list
	 * @param dir
	 *            the dir
	 * @param withDirectories
	 *            the with directories
	 * @param withFiles
	 *            the with files
	 * @param depth
	 *            the depth
	 * @param search
	 *            the search
	 */
	private void setDirectory(final URI baseuri, final List<FileInfoType> list,
			File dir, boolean withDirectories, boolean withFiles,
			final int depth, String search) {
		if (depth <= 0)
			return;

		if (search == null || search.equals("")) {
			search = "*";
			logger.info("No search pattern supplied, using default '*'.");
		}

		FileWalker finder = new FileWalker(search);
		try {
			Files.walkFileTree(dir.toPath(),
					EnumSet.of(FileVisitOption.FOLLOW_LINKS), depth, finder);
			logger.info("FileWalker returned '" + finder.getCount()
					+ "' hits. '" + finder.getTotal()
					+ "' files have been scanned.");
			List<Path> result = finder.getResult();
			for (Path f : result) {
				if (!withDirectories) {
					if (f.toFile().isDirectory())
						continue;
				}
				if (!withFiles) {
					if (f.toFile().isFile())
						continue;
				}
				list.add(new FileInfoType(f, baseuri));
			}
		} catch (IOException e2) {
			logger.error(e2.getMessage(), e2);
			;
		}
	}

	/**
	 * Sets the directory.
	 * 
	 * @param dir
	 *            the dir
	 * @param withDirectories
	 *            the with directories
	 * @param withFiles
	 *            the with files
	 * @param depth
	 *            the depth
	 * @param search
	 *            the search
	 * @return the list
	 */
	private List<FileInfoType> setDirectory(URI baseuri, String dir,
			boolean withDirectories, boolean withFiles, int depth, String search) {
		List<FileInfoType> list = new ArrayList<FileInfoType>();
		setDirectory(baseuri, list, new File(dir), withDirectories, withFiles,
				depth, search);
		return list;
	}

	private boolean isPermitted(String dir) {
		/*
		 * 
		 * logger.warn(String.format(
		 * "User '%s' does not have permission to access '%s'."
		 * ,SecurityUtils.getSubject().getPrincipal(), dir )); throw new
		 * NotAuthorizedException(new
		 * UnauthorizedException("User does not have permission to access "+
		 * dir)); }
		 */
		return true;
	}

	// http://stackoverflow.com/questions/3758606/how-to-convert-byte-size-into-human-readable-format-in-java
	private static String humanReadableByteCount(long bytes, boolean si) {
		int unit = si ? 1000 : 1024;
		if (bytes < unit)
			return bytes + " B";
		int exp = (int) (Math.log(bytes) / Math.log(unit));
		String pre = (si ? "kMGTPE" : "KMGTPE").charAt(exp - 1)
				+ (si ? "" : "i");
		return String.format("%.1f %sB", bytes / Math.pow(unit, exp), pre);
	}

	private DimensionsConnection getDIMCMConnection() {
		/*
		 * Do we have a registered connection already?
		 */
		DimensionsConnection conn = null;
		
		try {
			conn = DimensionsConnectionManager.getThreadConnection();
			if (conn != null)
				return conn;
		} catch (Exception e) {
			logger.error(e.getMessage());
		}

		/*
		 * Create a new connection from property file
		 */
		PropertiesConfiguration props;
		try {
			
			props = new Configurations().properties(this.getClass()
					.getClassLoader().getResource("/../dimcm.properties"));
		} catch (ConfigurationException e) {
			e.printStackTrace();
			return null;
		}

		DimensionsConnectionDetails details = new DimensionsConnectionDetails();
		details.setUsername(props.getString("user"));
		details.setPassword(props.getString("password"));
		details.setDbName(props.getString("dbname"));
		details.setDbConn(props.getString("dbconn"));
		details.setServer(props.getString("server"));
		conn = DimensionsConnectionManager.getConnection(details);
		DimensionsConnectionManager.registerThreadConnection(conn);
		return conn;
	}

	private String getBaseURL() {
		final String CACHE_BASEURL = "DIMCM.conf.baseurl";
	try {
		CacheAccess<Object, Object> cache = JCS.getInstance("DIMCM");
		String baseurl = (String) cache.get(CACHE_BASEURL);
		if(baseurl != null) return baseurl;
		
		Configurations configs = new Configurations();
		
		PropertiesConfiguration	props = configs.properties((this.getClass().getClassLoader().getResource("/../dimcm.properties")));
		baseurl = props.getString("baseurl");
		cache.put(CACHE_BASEURL, baseurl);
		return baseurl;
		
	} catch (CacheException e1) {
		// TODO Auto-generated catch block
		e1.printStackTrace();
		return null;
	} catch (ConfigurationException e) {
		e.printStackTrace();
		return null;
		
	}
}
}