/*******************************************************************************
 * Copyright 2Fi Business Solutions Ltd.
 *
 * This code is copyrighted. Under no circumstances should any party, people,
 * or organization should redistribute any portions of this code in any form,
 * either verbatim or through electronic media, to any third parties, unless
 * under explicit written permission by 2Fi Business Solutions Ltd.
 ******************************************************************************/
package com.ffii.tbms.api.web;

import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.imageio.ImageIO;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.ffii.core.setting.service.SettingsService;
import com.ffii.core.utils.FileUtils;
import com.ffii.core.utils.MapUtils;
import com.ffii.core.utils.NumberUtils;
import com.ffii.core.utils.Params;
import com.ffii.core.utils.StringUtils;
import com.ffii.core.utils.web.ServletRequestUtils;
import com.ffii.core.web.AbstractController;
import com.ffii.core.web.view.AbstractView;
import com.ffii.core.web.view.json.JsonView;
import com.ffii.tbms.api.service.AuthenticationService;
import com.ffii.tbms.api.service.OrderApiService;
import com.ffii.tbms.file.File;
import com.ffii.tbms.file.FileBlob;
import com.ffii.tbms.file.FileRef;
import com.ffii.tbms.file.FileRefType;
import com.ffii.tbms.file.service.FileService;
import com.ffii.tbms.log.LogUtils;
import com.ffii.tbms.log.service.AuditLogService;
import com.ffii.tbms.order.Order;
import com.ffii.tbms.order.service.OrderService;

import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;

@Controller
@RequestMapping(value = "/api/file")
public class FileApiController extends AbstractController {

	@Autowired
	private AuthenticationService authenticationService;

	@Autowired
	OrderApiService orderApiService;

	@Autowired
	private FileService fileService;

	@Autowired
	private SettingsService settingsService;

	@Autowired
	private AuditLogService auditLogService;

	private static final long MAX_FILE_UPLOAD_SIZE = 25 * 1024 * 1024; // 25 MB

	private static final boolean OVERWRITE_SAME_FILENAME = false;

	private static final int DEFAULT_UPLOAD_MAX_FILE_SIZE_MB = 50;

	@RequestMapping(value = "/list.json", method = { RequestMethod.GET, RequestMethod.POST })
	public String listJson(Model model, @RequestParam String deviceId, @RequestParam String token,
			@RequestParam String refType, @RequestParam Integer refId) {

		Map<String, Object> data = authenticationService.findUserByAccessToken(deviceId, token);
		if (data == null) {
			model.addAllAttributes(authenticationService.getNoUserFoundMap());
			return JsonView.NAME;
		}

		Map<String, Object> args = new HashMap<String, Object>();
		args.put(Params.REF_TYPE, refType);
		args.put(Params.REF_ID, refId);

		List<Map<String, Object>> records = fileService.searchFiles(args);

		model.addAttribute(Params.RECORDS, records);
		model.addAttribute(Params.SUCCESS, Boolean.TRUE);

		return JsonView.NAME;
	}

	@RequestMapping(value = "/measurement-sheet/ul", method = RequestMethod.POST)
	public String uploadMeasurementSheetFile(HttpServletRequest request, Model model, @RequestParam String deviceId, @RequestParam String token,
			@RequestParam int orderId, @RequestParam MultipartFile multipartFile) throws Exception {

		Map<String, Object> userData = authenticationService.findUserByAccessToken(deviceId, token);
		if (userData == null) {
			model.addAllAttributes(authenticationService.getNoUserFoundMap());
			return JsonView.NAME;
		}

		logger.info("upload file start");
		Order order = orderApiService.find(orderId);
		String refType = FileRefType.MEASUREMENT_SHEET;
		String refCode = order.getCustId() + "";
		Integer refId = orderId;

		// get file upload max file size setting
		int uploadMaxFileSize = settingsService.getInt("FILE.upload.maxFileSize", DEFAULT_UPLOAD_MAX_FILE_SIZE_MB) * 1024 * 1024;

		Boolean success = Boolean.TRUE;

		// only proceed if multipartFile is not null, and has file size
		if (multipartFile != null && multipartFile.getSize() > 0 && multipartFile.getSize() <= uploadMaxFileSize) {

			// DEBUG LOG
			logger.info("multipartFile.getSize() = " + multipartFile.getSize());

			File file = new File();
			file.setSysGroupId(NumberUtils.toInt(userData.get("sysGroupId"),0));
			file.setFilename(multipartFile.getOriginalFilename());
			// file.setMimetype(multipartFile.getContentType());
			file.setMimetype(FileUtils.guessMimetype(file.getFilename()));
			file.setFilesize(multipartFile.getSize());
			file.setDescription(ServletRequestUtils.getTrimmedStringParameter(request, "description"));

			FileBlob fileBlob = new FileBlob();
			fileBlob.setBytes(multipartFile.getBytes());
			fileBlob.setSysGroupId(NumberUtils.toInt(userData.get("sysGroupId"),0));

			FileRef fileRef = new FileRef();
			fileRef.setRefId(refId);
			fileRef.setRefType(refType);
			fileRef.setRefCode(refCode);
			fileRef.setSysGroupId(NumberUtils.toInt(userData.get("sysGroupId"),0));

			// get height and width if mimetype is png or jpeg
			if (AbstractView.CONTENT_TYPE_PNG.equals(file.getMimetype()) || AbstractView.CONTENT_TYPE_JPEG.equals(file.getMimetype())) {
				BufferedImage image = ImageIO.read(new ByteArrayInputStream(fileBlob.getBytes()));
				if (image != null) {
					file.setImageHeight(image.getHeight());
					file.setImageWidth(image.getWidth());
				}
			}

			if (OVERWRITE_SAME_FILENAME) {
				// search for existing file(s) with the same refType, refId, and filename
				List<Map<String, Object>> existingFiles = fileService.searchFiles(
						MapUtils.toHashMap("refType", refType, "refId", refId, "filename", file.getFilename()));

				// delete them if found
				for (Map<String, Object> existingFile : existingFiles) {
					fileService.deleteFile((Integer) existingFile.get("id"), (Integer) existingFile.get("refId"),
							(String) existingFile.get("refType"), (String) existingFile.get("skey"));
				}
			}

			// create UserFile
			file.setSkey(RandomStringUtils.randomAlphanumeric(16));
			fileService.saveFile(file);

			// create UserFileBlob
			fileBlob.setFileId(file.getId());
			fileService.saveFileBlob(fileBlob);

			// create UserFileRef
			fileRef.setFileId(file.getId());
			fileService.saveFileRef(fileRef);

			order.setFileId(file.getId());
			orderApiService.saveOrUpdate(order);
		} else {
			success = Boolean.FALSE;

			// if not success, return msg to client
			model.addAttribute(Params.MSG, getMessageSourceAccessor().getMessage("Upload Failed"));
		}

		model.addAttribute(Params.SUCCESS, success);

		return JsonView.NAME;
	}

	@RequestMapping(value = "/ul", method = RequestMethod.POST)
	public String uploadFile(HttpServletRequest request, Model model, @RequestParam String deviceId,
			@RequestParam String token, @RequestParam int refId, @RequestParam String refType,
			@RequestParam(defaultValue = StringUtils.EMPTY) String refCode, @RequestParam MultipartFile multipartFile)
			throws IOException {

		Map<String, Object> userData = authenticationService.findUserByAccessToken(deviceId, token);
		if (userData == null) {
			model.addAllAttributes(authenticationService.getNoUserFoundMap());
			return JsonView.NAME;
		}
	
		// only proceed if multipartFile is not null, and has file size
		if(multipartFile == null || multipartFile.getSize() <= 0 || multipartFile.getSize() > MAX_FILE_UPLOAD_SIZE){
			model.addAttribute(Params.MSG, getMessageSourceAccessor().getMessage("FILE.msg.uploadFailed"));
			model.addAttribute(Params.SUCCESS, Boolean.FALSE);
			return JsonView.NAME;
		}

		// DEBUG LOG
		logger.info("multipartFile.getSize() = " + multipartFile.getSize());

		File file = new File();
		file.setSysGroupId(NumberUtils.toInt(userData.get("sysGroupId"),0));
		file.setFilename(multipartFile.getOriginalFilename());
		// file.setMimetype(multipartFile.getContentType());
		file.setMimetype(FileUtils.guessMimetype(file.getFilename()));
		file.setFilesize(multipartFile.getSize());

		FileBlob fileBlob = new FileBlob();
		fileBlob.setSysGroupId(NumberUtils.toInt(userData.get("sysGroupId"),0));
		fileBlob.setBytes(multipartFile.getBytes());

		FileRef fileRef = new FileRef();
		fileRef.setSysGroupId(NumberUtils.toInt(userData.get("sysGroupId"),0));
		fileRef.setRefId(refId);
		fileRef.setRefType(refType);
		fileRef.setRefCode(refCode);

		// get height and width if mimetype is png or jpeg
		if (AbstractView.CONTENT_TYPE_PNG.equals(file.getMimetype())
				|| AbstractView.CONTENT_TYPE_JPEG.equals(file.getMimetype())) {
			BufferedImage image = ImageIO.read(new ByteArrayInputStream(fileBlob.getBytes()));
			if (image != null) {
				file.setImageHeight(image.getHeight());
				file.setImageWidth(image.getWidth());
			}
		}

		// search for existing file(s) with the same refType, refId, and filename
		List<Map<String, Object>> existingFiles = fileService.searchFiles(
				MapUtils.toHashMap("refType", refType, "refId", refId, "filename", file.getFilename()));

		// delete them if found
		for (Map<String, Object> existingFile : existingFiles) {
			fileService.deleteFile((Integer) existingFile.get("id"), (Integer) existingFile.get("refId"),
					(String) existingFile.get("refType"), (String) existingFile.get("skey"));
		}

		// create UserFile
		file.setSkey(RandomStringUtils.randomAlphanumeric(16));
		fileService.saveFile(file);

		// create UserFileBlob
		fileBlob.setFileId(file.getId());
		fileService.saveFileBlob(fileBlob);

		// create UserFileRef
		fileRef.setFileId(file.getId());
		fileService.saveFileRef(fileRef);

		Map<String, Object> map = new HashMap<>();
		map.put("skey", file.getSkey());
		map.put("fileId", file.getId());
		map.put("filename", file.getFilename());
		map.put("filesize", file.getFilesize());
		map.put("refType", fileRef.getRefType());
		map.put("refId", fileRef.getRefId());
		map.put("mimetype", file.getMimetype());

		model.addAttribute(Params.DATA, map);

		if(refType.equals(FileRefType.ORDER)){
			ObjectMapper mapper = new ObjectMapper();
			auditLogService.save("orders",
				refId,
				NumberUtils.intValue(userData.get("id")),
				new Date(),
				LogUtils.ACTION_CREATE,
				mapper.writeValueAsString(MapUtils.toHashMap("photoFileId", file.getId(), "filename",file.getFilename())));
		}

		model.addAttribute(Params.AUTH, Boolean.TRUE);
		model.addAttribute(Params.SUCCESS, Boolean.TRUE);

		return JsonView.NAME;
	}

	@RequestMapping(value = "/dl/{id}/{skey}/{filename}", method = RequestMethod.GET)
	public void download(HttpServletResponse response, Model model, @RequestParam(defaultValue = "false") boolean dl,
			@PathVariable int id, @PathVariable String skey) throws IOException {

		File file = fileService.findFileByIdAndKey(id, skey);

		if (file != null) {
			FileBlob fileBlob = fileService.findFileBlobByFileId(id);

			response.reset();
			response.setContentType(file.getMimetype());
			response.setContentLength((int) file.getFilesize());
			response.setHeader("Content-Transfer-Encoding", "binary");
			response.setHeader("Content-Disposition", String.format("%s; filename=\"%s\"", dl ? "attachment" : "inline",
					response.encodeURL(file.getFilename())));

			ServletOutputStream out = response.getOutputStream();

			try {
				out.write(fileBlob.getBytes());
				out.flush();
				out.close();
			} catch (IOException e) {
				logger.warn(e.getMessage());
			} finally {
				out.close();
				model.addAttribute(Params.SUCCESS, Boolean.TRUE);
			}
		} else {
			logger.info("*** 400 BAD REQUEST ***");
			response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
			model.addAttribute(Params.SUCCESS, Boolean.FALSE);
		}
	}

	@RequestMapping(value = "/delete", method = RequestMethod.POST)
	public String delete(HttpServletResponse response, Model model, @RequestParam String deviceId,
			@RequestParam String token, @RequestParam Integer fileId, @RequestParam Integer refId,
			@RequestParam String refType, @RequestParam String skey) throws IOException {

		Map<String, Object> userData = authenticationService.findUserByAccessToken(deviceId, token);
		if (userData == null) {
			model.addAllAttributes(authenticationService.getNoUserFoundMap());
			return JsonView.NAME;
		}

		fileService.deleteFile(fileId, refId, refType, skey);

		if(refType.equals(FileRefType.MEASUREMENT_SHEET)){
			ObjectMapper mapper = new ObjectMapper();
			auditLogService.save("orders",
				refId,
				NumberUtils.intValue(userData.get("id")),
				new Date(),
				LogUtils.ACTION_DELETE,
				mapper.writeValueAsString(MapUtils.toHashMap("fileId", fileId)));
		}else if(refType.equals(FileRefType.ORDER)){
			ObjectMapper mapper = new ObjectMapper();
			auditLogService.save("orders",
				refId,
				NumberUtils.intValue(userData.get("id")),
				new Date(),
				LogUtils.ACTION_DELETE,
				mapper.writeValueAsString(MapUtils.toHashMap("photoFileId", fileId)));
		}

		model.addAttribute(Params.SUCCESS, Boolean.TRUE);

		return JsonView.NAME;
	}

	@RequestMapping(value = "/thumbnail/{id}/{skey}/{filename}", method = RequestMethod.GET)
	public void thumbnail(HttpServletResponse response, Model model, @RequestParam(defaultValue = "false") boolean dl,
			@PathVariable int id, @PathVariable String skey) throws IOException {

		File file = fileService.findFileByIdAndKey(id, skey);

		if (file != null) {
			FileBlob fileBlob = fileService.findFileBlobByFileId(id);

			response.reset();
			response.setContentType(file.getMimetype());
			// response.setContentLength((int) file.getFilesize());
			response.setHeader("Content-Transfer-Encoding", "binary");
			response.setHeader("Content-Disposition", String.format("%s; filename=\"%s\"", dl ? "attachment" : "inline",
					response.encodeURL(file.getFilename())));

			int limit = 500;
			BufferedImage image = ImageIO.read(new ByteArrayInputStream(fileBlob.getBytes()));
			int width = image.getWidth();
			int height = image.getHeight();
			if (width > height) {
				if (width > limit) {
					height = height * limit / width;
					width = limit;
				}
			} else {
				if (height > limit) {
					width = width * limit / height;
					height = limit;
				}
			}
			image = scale(image, width, height);
			ByteArrayOutputStream tmp = new ByteArrayOutputStream();
			ImageIO.write(image, "jpg", tmp);
			tmp.close();
			response.setContentLength((int) tmp.size());

			ServletOutputStream out = response.getOutputStream();

			try {
				out.write(tmp.toByteArray());
				out.flush();
				out.close();
			} catch (IOException e) {
				logger.warn(e.getMessage());
			} finally {
				out.close();
				model.addAttribute(Params.SUCCESS, Boolean.TRUE);
			}
		} else {
			logger.info("*** 400 BAD REQUEST ***");
			response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
			model.addAttribute(Params.SUCCESS, Boolean.FALSE);
		}
	}

	static BufferedImage scale(BufferedImage originalImage, int w, int h) {
		BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
		int x, y;
		int ww = originalImage.getWidth();
		int hh = originalImage.getHeight();
		for (x = 0; x < w; x++) {
			for (y = 0; y < h; y++) {
				int col = originalImage.getRGB(x * ww / w, y * hh / h);
				img.setRGB(x, y, col);
			}
		}
		return img;
	}


}
