package info.bukova.isspst.storage;

import info.bukova.isspst.dao.QueryDao;
import info.bukova.isspst.data.FileContent;
import info.bukova.isspst.data.FileMetainfo;
import info.bukova.isspst.services.fulltext.Extractor;
import info.bukova.isspst.services.fulltext.ExtractorFactory;
import org.apache.commons.codec.binary.Hex;
import org.hibernate.Query;
import org.hibernate.SQLQuery;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.List;
import java.util.UUID;

/**
 * @author Pepa Rokos
 *
 * Třída pro práci s uložištěm příloh u záznamů. Na soubory příloh se odkazuje pomocí objektů FileMetainfo.
 *
 * @see info.bukova.isspst.data.FileMetainfo
 */
public class DocumentFileStorageImpl extends AbstractFileStorage<FileMetainfo> implements DocumentFileStorage {

	private String rootPath;
	@Autowired
	private QueryDao queryDao;

	/**
	 * Nastavuje kořenová adresář pro ukládání příloh. Nastavuje se absolutní cesta na filesystému serveru.
	 *
	 * @param rootPath kořenový adresář pro přílohy
	 */
	public void setRootPath(String rootPath) {
		this.rootPath = rootPath;
	}

	@Override
	public String getRootPath() {
		return this.rootPath;
	}

	@Override
	public void saveFile(byte[] data, FileMetainfo fileID) {
		String fileName = generateFileName(fileID.getFileName());

		saveFileDataToPath(data, rootPath + File.separator + fileName);
		fileID.setPathInFilesystem(fileName);
		fileID.setContentType(MimeTypes.getContentType(data, fileName));
	}

	@Override
	public void saveFile(File file, FileMetainfo fileID) {
		String fileName = generateFileName(fileID.getFileName());

		saveFileToPtah(file, rootPath + File.separator + fileName);
		fileID.setPathInFilesystem(fileName);
		fileID.setContentType(MimeTypes.getContentType(readFile(file), fileName));
	}

	private String generateFileName(String originalName) {
		String extension = null;
		String fileName = UUID.randomUUID().toString();

		if (originalName != null && !originalName.isEmpty()) {
			extension = MimeTypes.fileExtension(originalName);
		}

		if (extension != null) {
			fileName = fileName + "." + extension;
		}

		return fileName;
	}

	private void extractContent(InputStream is, FileMetainfo fileID) {
		Extractor extractor = ExtractorFactory.createExtractor(fileID.getContentType());

		if (extractor != null) {
			fileID.getContent().setPlainText(extractor.extract(is));
		}

	}

	@Override
	@Transactional
	public void removeFile(FileMetainfo fileID) {
		if (fileID.getPathInFilesystem() == null || fileID.getPathInFilesystem().isEmpty()) {
			return;
		}

		if (infosForPath(fileID.getPathInFilesystem()).size() <= 1) {
			removeFileByPath(rootPath + File.separator + fileID.getPathInFilesystem());
		}
	}

	@Override
	public void moveFile(String source, String destination) {
		throw new UnsupportedOperationException();
	}

	@Override
	public void createDirectory(String dir) {
		throw new UnsupportedOperationException();
	}

	@Override
	public byte[] fileData(FileMetainfo fileID) {
		if (fileID.getPathInFilesystem() != null && !fileID.getPathInFilesystem().isEmpty()) {
			return fileDataFromPath(rootPath + File.separator + fileID.getPathInFilesystem());
		}

		return null;
	}

	@Override
	public File file(FileMetainfo fileID) {
		if (fileID.getPathInFilesystem() != null && !fileID.getPathInFilesystem().isEmpty()) {
			return fileFromPath(rootPath + File.separator + fileID.getPathInFilesystem());
		}

		return null;
	}

	@Override
	public boolean dirExists(String path) {
		throw new UnsupportedOperationException();
	}

	@Override
	public String serverPath(FileMetainfo fileID) {
		return "/api/dl/" + fileID.getPathInFilesystem() + "/" + fileID.getFileName();
	}

	@Override
	@Transactional
	public FileMetainfo saveAndCreateInfo(byte[] data, String name) {
		FileMetainfo metaInfo = new FileMetainfo();
		metaInfo.setFileName(name);

		if (!checkForDuplicate(new ByteArrayInputStream(data), metaInfo)) {
			saveFile(data, metaInfo);
			extractContent(new ByteArrayInputStream(data), metaInfo);
		}

		return metaInfo;
	}

	@Override
	@Transactional
	public FileMetainfo saveAndCreateInfo(File file, String name) {
		FileMetainfo metaInfo = new FileMetainfo();
		metaInfo.setFileName(name);

		try {
			if (!checkForDuplicate(new FileInputStream(file), metaInfo)) {
				saveFile(file, metaInfo);
				extractContent(new FileInputStream(file), metaInfo);
			}
		} catch (FileNotFoundException e) {
			//TODO: ošetřit
			e.printStackTrace();
		}

		return metaInfo;
	}

	@Override
	@Transactional
	public FileMetainfo getMetainfoForPath(String path) {
		if (infosForPath(path).isEmpty()) {
			return null;
		}

		return infosForPath(path).get(0);
	}

	private List<FileMetainfo> infosForPath(String path) {
		Query q = queryDao.getQuery("from FileMetainfo info where info.content.pathInFilesystem = :path");
		q.setString("path", path);

		return q.list();
	}

	private boolean checkForDuplicate(InputStream is, FileMetainfo info) {
		String md5 = null;
		try {
			MessageDigest md = MessageDigest.getInstance("MD5");
			byte[] dataBytes = new byte[1024];
			int nread = 0;

			while ((nread = is.read(dataBytes)) != -1) {
				md.update(dataBytes, 0, nread);
			}

			md5 = new String(Hex.encodeHex(md.digest()));
		} catch (NoSuchAlgorithmException e) {
			//TODO: ošetřit
			e.printStackTrace();
			return false;
		} catch (IOException e) {
			e.printStackTrace();
		}

		Query q = queryDao.getQuery("select info from FileMetainfo info where info.md5 = :md5");
		q.setString("md5", md5);
		List<FileMetainfo> found = (List<FileMetainfo>) q.list();

		if (!found.isEmpty()) {
			FileMetainfo foundInfo = found.get(0);
			info.setMd5(foundInfo.getMd5());
			info.setContent(foundInfo.getContent());

			return true;
		} else {
			info.setMd5(md5);
			return false;
		}
	}

	@Override
	@Transactional
	public void removeAttachment(EntityWithAttachment entity, FileMetainfo metaInfo) {
		entity.removeAttachment(metaInfo);
		removeFile(metaInfo);
	}

	@Override
	@Transactional
	public void removeAllAttachments(EntityWithAttachment entity) {
		for (FileMetainfo metaInfo : entity.getAttachedFiles()) {
			removeFile(metaInfo);
		}

		entity.getAttachedFiles().clear();
	}

	@Override
	@Transactional
	public void purge() {
		File folder = new File(rootPath);
		File[] allFiles = folder.listFiles();
		Query q = queryDao.getQuery("from FileMetainfo");
		List<FileMetainfo> infos = q.list();

		for (File f : allFiles) {
			boolean fileExists = false;

			for (FileMetainfo info : infos) {
				FileContent fileContent = info.getContent();
				
				if ((fileContent != null) && f.isFile() && fileContent.getPathInFilesystem().equals(f.getName()))
				{
					fileExists = true;
					break;
				}
			}

			if (!fileExists && f.isFile())
			{
				f.delete();
			}
		}

		SQLQuery sql = queryDao.getSession().createSQLQuery("delete from FILE_CONTENTS where ID not in(select CONTENT_ID from FILE_METAINFO)");
		sql.executeUpdate();
	}
}
