/**
 *  
 */
package core.services;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Transparency;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;

import org.jcodec.api.awt.FrameGrab;
import org.jcodec.common.model.Picture;
import org.jcodec.scale.AWTUtil;

import core.services.ApiService.OKResponseObject;
import models.Media;
import models.Media.MediaType;
import models.Test.TestType;
import models.User;
import play.Logger;
import play.mvc.Router;
import play.mvc.results.*;
import utils.FileUploadManager;
import utils.FileUtil;
import utils.StringUtil;

/**
 *
 * @author luca
 */
public class MediaService {

	private static enum MediaServiceSingleton {
		INSTANCE;

		MediaService singleton = new MediaService();

		public MediaService getSingleton() {
			return singleton;
		}
	}
	
	private MediaService() {
		populateTypes();
	}

	public static MediaService instance() {
		return MediaService.MediaServiceSingleton.INSTANCE.getSingleton();
	}

	public static enum Thumb {
		AUDIO_THUMB, TEXT_THUMB, VIDEO_THUMB, PHOTO_THUMB;
	}

	private Map<String, MediaType> extensionMap;
	
	private static ApiService apiService = ApiService.instance();
	private static UserService userService = UserService.instance();
	private static FileUploadManager fileUploadManager = FileUploadManager.instance();
	private static Map<String, Media> publicMap = new HashMap<String, Media>();
	private static Map<String, String> publicMediaMap = new HashMap<String, String>();
	private static Map<String, Calendar> publicMediaDate = new HashMap<String, Calendar>();
	
	public static class MediaObject {
		public String mediaId;
		public String mediaType;
		public String mediaName;
		public String mediaPath;
		public String mediaThumbPath;
		public String mediaAuthorId;
		public String mediaAuthorName;
		public String mediaDate;
		public Boolean toEdit;
		public String publicUrl;
	}

	private void populateTypes(){
		if(this.extensionMap==null){
			extensionMap = new HashMap<String,MediaType>();
		}
		extensionMap.put("jpg", MediaType.PHOTO);
		extensionMap.put("jpeg", MediaType.PHOTO);
		extensionMap.put("png", MediaType.PHOTO);
		extensionMap.put("bmp", MediaType.PHOTO);
		extensionMap.put("flv", MediaType.VIDEO);
		extensionMap.put("avi", MediaType.VIDEO);
		extensionMap.put("mp4", MediaType.VIDEO);
		extensionMap.put("mpg", MediaType.VIDEO);
		extensionMap.put("wmv", MediaType.VIDEO);
		extensionMap.put("mp3", MediaType.AUDIO);
		extensionMap.put("waw", MediaType.AUDIO);
		extensionMap.put("wma", MediaType.AUDIO);
		extensionMap.put("txt", MediaType.TEXT);
		extensionMap.put("csv", MediaType.TEXT);
	}
	
	public static class MediaListResponse extends OKResponseObject {
		public List<MediaObject> response;
	}

	public String mediaList(String mediaType, String mediaName, String mediaAuthor, String mediaDate,
			String mediaTestId, String tt) {
		if (!StringUtil.isNil(mediaType)) {
			MediaType check = MediaType.tryBuildMediaTypeFromValue(mediaType);
			if (check == null) {
				return apiService.buildNotValidMediaTypeErrorResponse();
			}
		}
		
		if (!StringUtil.isNil(tt)) {
			TestType check = TestType.tryBuildTestTypeFromName(tt);
			if (check == null) {
				return apiService.buildNotValidMediaTypeErrorResponse();
			}
		}
		boolean isAdmin;
		if (userService.hasCurrentUserLoggedAdminRole()) {
			isAdmin = true;
		} else {
			isAdmin = false;
		}
		List<Media> mediaList = Media.findAllFilteredByMediaTypeAndMediaNameAndMediaAuthorAndMediaDateAndMediaTestId(
				isAdmin, mediaType, mediaName, mediaAuthor, mediaDate, mediaTestId, tt);
		MediaListResponse res = new MediaListResponse();
		res.response = buildMediaListObjectFromMediaList(mediaList);
		String ret = res.toJson();
		return ret;
	}

	public List<MediaObject> buildMediaListObjectFromMediaList(List<Media> mediaList) {
		List<MediaObject> ret = new LinkedList<MediaObject>();
		if (mediaList != null && !mediaList.isEmpty()) {
			for (Media media : mediaList) {
				MediaObject mediaObject = buildMediaObjectFromMedia(media);
				ret.add(mediaObject);
			}
		}
		return ret;
	}

	public MediaObject buildMediaObjectFromMedia(Media media) {
		MediaObject mediaObject = new MediaObject();
		if (media.getMediaAuthor() != null) {
			mediaObject.mediaAuthorId = media.getMediaAuthor().getUuid();
			mediaObject.mediaAuthorName = media.getMediaAuthor().getName();
		}
		mediaObject.mediaDate = StringUtil.date(media.getCreatedAt(), Media.CREATED_AT_DATE_FORMAT);
		mediaObject.mediaId = media.getUuid();
		mediaObject.mediaName = media.getMediaName();
		Map<String, Object> args = new LinkedHashMap<>();
		args.put("repositoryId", media.getRepositoryId());
		args.put("thumb", false);
		mediaObject.mediaPath = Router.reverse("MediaController.retrieve", args).url;
		if (media.getMediaType().equals(MediaType.VIDEO) || media.getMediaType().equals(MediaType.PHOTO)
				|| !StringUtil.isNil(media.getThumbRepositoryId())) {
			if (!StringUtil.isNil(media.getThumbRepositoryId())) {
				if (!media.getMediaType().equals(MediaType.TEXT) && !media.getMediaType().equals(MediaType.AUDIO)) {
					args.put("repositoryId", media.getThumbRepositoryId());
					args.put("thumb", true);
				} else {
					args.clear();
					if (media.getMediaType().equals(MediaType.AUDIO)) {
						args.put("thumbId", Thumb.AUDIO_THUMB.name());
					} else {
						args.put("thumbId", Thumb.TEXT_THUMB.name());
					}
				}
			} else if (media.getMediaType().equals(MediaType.VIDEO)) {
				args.clear();
				args.put("thumbId", Thumb.VIDEO_THUMB.name());
			} else if (media.getMediaType().equals(MediaType.PHOTO)) {
				args.clear();
				args.put("thumbId", Thumb.PHOTO_THUMB.name());
			}
			mediaObject.mediaThumbPath = Router.reverse("MediaController.retrieve", args).url;
		}
		mediaObject.mediaType = media.getMediaType().name();
		if (userService.hasCurrentUserLoggedAdminRole()) {
			mediaObject.toEdit = true;
		} else {
			User currentUserLogged = userService.getCurrentUserLogged();
			if (currentUserLogged.equals(media.getMediaAuthor())) {
				mediaObject.toEdit = true;
			} else {
				mediaObject.toEdit = false;
			}
		}
		if(media.getMediaType().equals(MediaType.VIDEO)){
			String code = UUID.randomUUID().toString();
			instance().publicMap.put(code, media);
			instance().publicMediaMap.put(media.getRepositoryId(), code);
			Calendar c = new GregorianCalendar();
			c.add(Calendar.HOUR, 1);
			instance().publicMediaDate.put(media.getRepositoryId(), c);
			mediaObject.publicUrl = code;
		}
		
		return mediaObject;
	}

	public static class MediaDeleteRequest {
		public String mediaId;
	}

	public static class MediaDeleteResponse extends OKResponseObject {
		public Object response = new Object();
	}

	public String deleteMedia(String mediaId) {

		if (StringUtil.isNil(mediaId)) {
			return apiService.buildWrongEndpointErrorResponse();
		}

		Media media = Media.findById(mediaId);
		if (media == null) {
			return apiService.buildEntityDoesNotExistsErrorResponse();
		}

		if (!userService.hasCurrentUserLoggedAdminRole()) {
			User currentUserLogged = userService.getCurrentUserLogged();
			if (!currentUserLogged.equals(media.getMediaAuthor())) {
				return apiService.buildNotAuthorizedResponse();
			}
		}

		if (media.getDeleted()) {
			return apiService.buildEntityAlreadyDeletedErrorResponse();
		}

		media.setDeleted(true);
		media.save();

		/* move temporary media file in a deleted directory (in order to deny
		 media file and thumb as long as media is another time deleted = false
		 [there is no API to do this {updateMedia...}])*/

		MediaDeleteResponse res = new MediaDeleteResponse();
		String ret = res.toJson();
		return ret;
	}

	public static class MediaNewResponse extends OKResponseObject {
		public MediaObject response = new MediaObject();
	}

	public String mediaNew(File file, String tt, String mediaTestId) {

		Boolean isUser = false;
		String tempId = fileUploadManager.handleUploadTemporary(file);
		if (tempId == null) {
			return apiService.buildMandatoryParamsErrorResponse();
		}
		String subfolder = null;
		if(mediaTestId!=null && !mediaTestId.trim().equals("")){
			subfolder = mediaTestId;
			isUser = true;
		}
		String repositoryId = fileUploadManager.tempToRepositoryFile(subfolder, tempId);
		if (repositoryId == null) {
			return apiService.buildGenericErrorResponse();
		}
		
		TestType check = null;
		if (!StringUtil.isNil(tt)) {
			check = TestType.tryBuildTestTypeFromName(tt);
			if (check == null) {
				return apiService.buildNotValidMediaTypeErrorResponse();
			}
		}

		Media media = new Media();
		media.setCreatedAt(new Date());
		media.setMediaAuthor(userService.getCurrentUserLogged());
		media.setIsUser(isUser);
		media.setMediaName(file.getName());
		media.setRepositoryId(repositoryId);
		media.setTestType(check);
		File repositoryFile = fileUploadManager.getRepositoryFile(null, repositoryId);
		if (repositoryFile != null)
			media.setMediaPath(repositoryFile.getAbsolutePath());
		MediaType mediaType = buildMediaTypeFromExtensionFile(repositoryId);
		if (mediaType == null) {
			return apiService.buildMustSpecifyValidFileExtensionErrorResponse();
		}
		media.setMediaType(mediaType);
		File repositoryThumbnailFile = buildMediaThumbnailFile(repositoryFile, mediaType);
		if (repositoryThumbnailFile != null) {
			media.setThumbRepositoryId(repositoryThumbnailFile.getName());
			media.setThumbPath(repositoryThumbnailFile.getAbsolutePath());
		}
		media.save();
		MediaObject mediaObject = buildMediaObjectFromMedia(media);
		MediaNewResponse res = new MediaNewResponse();
		res.response = mediaObject;
		String ret = res.toJson();
		return ret;
	}
	

	private MediaType buildMediaTypeFromExtensionFile(String fileName) {
		if (StringUtil.isNil(fileName)) {
			return null;
		}
		String ext = FileUtil.extension(fileName);
		if (StringUtil.isNil(ext)) {
			return null;
		}
		ext = ext.toLowerCase();
		MediaType ret = extensionMap.get(ext);
		return ret;
		
		/*if (ext.equals("jpg") || ext.equals("png") || ext.equals("bmp")) {
			return MediaType.PHOTO;
		} else if (ext.equals("flv") || ext.equals("avi") || ext.equals("mp4") || ext.equals("mpg") || ext.equals("wmv")
				|| ext.equals("mpeg")) {
			return MediaType.VIDEO;
		} else if (ext.equals("mp3") || ext.equals("waw") || ext.equals("wma")) {
			return MediaType.AUDIO;
		} else if (ext.equals("txt") || ext.equals("csv")) {
			return MediaType.TEXT;
		} else {
			return null;
		}*/
	}

	private File buildMediaThumbnailFile(File repositoryFile, MediaType mediaType) {
		File mediaThumb = null;
		if (repositoryFile != null && mediaType != null) {
			if (mediaType.equals(MediaType.PHOTO)) {
				mediaThumb = createJpgFrom(repositoryFile);
				if (mediaThumb == null)
					mediaThumb = buildBasicThumb(MediaType.PHOTO);
			} else if (mediaType.equals(MediaType.VIDEO)) {
				mediaThumb = extractFirstVideoFrame(repositoryFile);
				if (mediaThumb == null)
					mediaThumb = buildBasicThumb(MediaType.VIDEO);
			} else if (mediaType.equals(MediaType.AUDIO) || mediaType.equals(MediaType.TEXT)) {
				mediaThumb = buildBasicThumb(mediaType);
			}
		}

		return mediaThumb;
	}

	private File buildBasicThumb(MediaType mediaType) {
		if (mediaType.equals(MediaType.AUDIO))
			return getThumb(Thumb.AUDIO_THUMB.name());
		else if (mediaType.equals(MediaType.TEXT))
			return getThumb(Thumb.TEXT_THUMB.name());
		else if (mediaType.equals(MediaType.VIDEO))
			return getThumb(Thumb.VIDEO_THUMB.name());
		else if (mediaType.equals(MediaType.PHOTO))
			return getThumb(Thumb.PHOTO_THUMB.name());
		else
			return null;
	}

	public File extractFirstVideoFrame(File repositoryFile) {
		File outputFile = null;
		String newExtension = "jpg";
		Picture picture = null;
		BufferedImage frame = null;
		try {
			picture = FrameGrab.getNativeFrame(repositoryFile, 0);
			frame = AWTUtil.toBufferedImage(picture);
			ImageIO.write(frame, "png", new File("frame_150.png"));
			ByteArrayOutputStream baos = new ByteArrayOutputStream();
			ImageIO.write(frame, newExtension, baos);
			baos.flush();
			String tempId = fileUploadManager.createTempFile(baos.toByteArray(), FileUtil.extension(repositoryFile));
			String repositoryId = fileUploadManager.tempToRepositoryFile(null, tempId, newExtension);
			outputFile = fileUploadManager.getRepositoryFile(null, repositoryId);
			return outputFile;
		} catch (Exception e) {
			Logger.error(e, "Exception during first video frame extraction... adding default thumb");
			return null;
		}
	}

	/* il repository contiene anche le thumb di PHOTO e VIDEO*/
	public static void retrieveFromRepository(String repositoryId, boolean thumb) {
		String partialLog;
		Media media = null;
		if (!thumb) {
			partialLog = "repositoryId";
			media = Media.findByRepositoryId(repositoryId);
		} else {
			partialLog = "thumbRepositoryId";
			media = Media.findByThumbRepositoryId(repositoryId);
		}
		if (media == null) {
			Logger.error("No media exists for " + partialLog + " %s", repositoryId);
			return;
		}
		if (!userService.hasCurrentUserLoggedAdminRole()) {
			User currentUserLogged = userService.getCurrentUserLogged();
			if (!currentUserLogged.equals(media.getMediaAuthor())) {
				Logger.error("User %s try to retrieveMediaFromRepository (" + partialLog + " %s) whose author is %s",
						currentUserLogged.getUuid(), repositoryId, media.getMediaAuthor().getUuid());
				return;
			}
		}
		if (!userService.hasCurrentUserLoggedAdminRole()) {
			User currentUserLogged = userService.getCurrentUserLogged();
			if (media.getDeleted()) {
				Logger.error("User %s try to retrieveMediaFromRepository deleted (" + partialLog + " %s)",
						currentUserLogged.getUuid(), repositoryId);
				return;
			}
		}
		if(media.getMediaType().equals(MediaType.VIDEO) && thumb==false){
			String code = "";
			if(instance().publicMediaMap.containsKey(media.getRepositoryId())){
				code = instance().publicMediaMap.get(media.getRepositoryId());
			} else {
				code = UUID.randomUUID().toString();
				instance().publicMap.put(code, media);
				instance().publicMediaMap.put(media.getRepositoryId(), code);
				Calendar c = new GregorianCalendar();
				c.add(Calendar.HOUR, 1);
				instance().publicMediaDate.put(media.getRepositoryId(),c);
			}
			String json = "{\"code\":\"" + code +"\"}";
			throw new RenderJson(json);
		}
		fileUploadManager.renderRepositoryFile(null, repositoryId);
	}
	
	public void retrievePublic(String code) {
		Media m = instance().publicMap.get(code);
		if(m != null) {
			fileUploadManager.renderRepositoryFile(null, m.getRepositoryId());
		}
	}

	public void retrieveThumb(String thumbId) {
		if (thumbId == null)
			return;
		if (!thumbId.equals(Thumb.AUDIO_THUMB.name()) && !thumbId.equals(Thumb.TEXT_THUMB.name())
				&& !thumbId.equals(Thumb.VIDEO_THUMB.name()) && !thumbId.equals(Thumb.PHOTO_THUMB.name())) {
			return;
		}
		File f = getThumb(thumbId);
		throw new RenderBinary(f);
	}

	private File getThumb(String thumbId) {
		if (StringUtil.isNil(thumbId))
			return null;
		String fileName = null;
		if (thumbId.equals(Thumb.AUDIO_THUMB.name())) {
			fileName = "audio-thumbnail.jpg";
		} else if (thumbId.equals(Thumb.TEXT_THUMB.name())) {
			fileName = "text-thumbnail.jpg";
		} else if (thumbId.equals(Thumb.VIDEO_THUMB.name())) {
			fileName = "video-default-thumbnail.png";
		} else if (thumbId.equals(Thumb.PHOTO_THUMB.name())) {
			fileName = "image-default-thumbnail.png";
		}
		File f = fileUploadManager.getRepositoryFile("Static_Thumbs", fileName);
		return f;
	}

	public File createJpgFrom(File input) {
		try {
			InputStream is = new FileInputStream(input);
			BufferedImage image = ImageIO.read(is);
			image = imageFillAlphaWithColor(image);
			Iterator<ImageWriter> jpgWriters = ImageIO.getImageWritersByFormatName("jpeg");
			ImageWriter jpgWriter = jpgWriters.next();
			ImageWriteParam iwp = jpgWriter.getDefaultWriteParam();
			iwp.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
			iwp.setCompressionQuality(0.50f);
			ByteArrayOutputStream baos = new ByteArrayOutputStream();
			jpgWriter.setOutput(ImageIO.createImageOutputStream(baos));
			jpgWriter.write(null, new IIOImage(image, null, null), iwp);
			String tempId = fileUploadManager.createTempFile(baos.toByteArray(), FileUtil.extension(input));
			String repositoryId = fileUploadManager.tempToRepositoryFile(null, tempId, "jpg");
			File outputFile = fileUploadManager.getRepositoryFile(null, repositoryId);
			return outputFile;
		} catch (Exception ex) {
			Logger.error(ex, "Error during jpg thumb creation... adding default thumb");
			return null;
		}
	}
	
	public static BufferedImage imageFillAlphaWithColor(BufferedImage image) {
		Color fillColor = new Color(255,255,255);
		
	    if (image.getColorModel().getTransparency() == Transparency.OPAQUE) return image;

	    int w = image.getWidth();
	    int h = image.getHeight();
	    BufferedImage newImage = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
	    Graphics2D g = newImage.createGraphics();
	    g.drawImage(image, 0, 0, fillColor, null);
	    g.dispose();
	    return newImage;
	}

	public void removeOldPublic() {
		Calendar c = new GregorianCalendar();
		List<String> toremove = new ArrayList<String>();
		for(String mid : publicMediaDate.keySet()){
			Calendar cc = publicMediaDate.get(mid);
			if(c.after(cc)){
				toremove.add(mid);
			}
		}
		
		for(String mid : toremove){
			String code = publicMediaMap.get(mid);
			if(code != null)
				publicMap.remove(code);
			publicMediaMap.remove(mid);
			publicMediaDate.remove(mid);
		}
		
	}
}