/**
 * License Agreement.
 *
 *  JBoss RichFaces - Ajax4jsf Component Library
 *
 * Copyright (C) 2007  Exadel, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License version 2.1 as published by the Free Software Foundation.
 *
 * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 */
package org.richfaces.realworld.manager;

import org.jboss.seam.Component;
import org.jboss.seam.ScopeType;
import org.jboss.seam.annotations.AutoCreate;
import org.jboss.seam.annotations.Create;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Scope;
import org.richfaces.realworld.domain.User;
import org.richfaces.realworld.service.Constants;

import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.ImageWriter;
import javax.imageio.stream.ImageInputStream;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Transparency;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Enumeration;
import java.util.zip.ZipEntry;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

@Name("fileManager")
@Scope(ScopeType.APPLICATION)
@AutoCreate
public class FileManager {

    private static final String _SMALL160 = "_small160";
    private static final String _SMALL120 = "_small120";
    private static final String _SMALL80 = "_small80";
    private static final String _SMALL200 = "_small200";
    private static final String _MEDIUM = "_medium";

    private static final String UPLOAD_ROOT_COMPONENT_NAME = "uploadRoot";
    private static final String UPLOAD_ROOT_PATH_COMPONENT_NAME = "uploadRootPath";

    private static final String IMAGE_JAR = "images.jar";

    private File uploadRoot;
    private String uploadRootPath;

   /* @Create
    public void create() {
        uploadRoot = (File) Component.getInstance(UPLOAD_ROOT_COMPONENT_NAME, ScopeType.APPLICATION);
        if (!uploadRoot.exists() && !uploadRoot.mkdir()) {
            logError("Can't create directory: " + uploadRoot.getName(), null);
        }

        uploadRootPath = (String) Component.getInstance(UPLOAD_ROOT_PATH_COMPONENT_NAME, ScopeType.APPLICATION);

        final JarFile jarFile;
        try {
            jarFile = new JarFile(this.getClass().getClassLoader()
                    .getResource("WEB-INF/lib/" + IMAGE_JAR).getPath());
        } catch (IOException e) {
            logError(IMAGE_JAR + " not found, please check it in WEB-INF/lib ", e);
            return;
        }

        extract(jarFile);
    }*/
    
    @Create
	public void create() {
		uploadRoot = (File)Component.getInstance(UPLOAD_ROOT_COMPONENT_NAME, ScopeType.APPLICATION);
		uploadRootPath = (String)Component.getInstance(UPLOAD_ROOT_PATH_COMPONENT_NAME, ScopeType.APPLICATION);
	}
	
	public void setUploadRoot(String uploadRootPath) throws IOException {
		if (uploadRootPath != null) {
			this.uploadRoot = new File(uploadRootPath);
			for (String f : uploadRoot.list()) {
				File temp = new File(uploadRoot, f);
				temp.delete();
			}
			uploadRoot.delete();
			uploadRoot.mkdirs();
			this.uploadRootPath = this.uploadRoot.getCanonicalPath()
			+ File.separator;
		} else {
			this.uploadRoot = null;
			this.uploadRootPath = null;
		}
	}

    private String joinFiles(String... files) {
        final StringBuilder res = new StringBuilder();
        for (String file : files) {
            res.append(file).append(File.pathSeparatorChar);
        }

        return res.substring(0, res.length() - 1);
    }

    private void logError(String s, Exception e) {
        if (s != null) {
            System.out.println(s);
        }

        if (e != null) {
            e.printStackTrace();
        }
    }

    public void extract(JarFile jarFile) {
        final Enumeration<JarEntry> entries = jarFile.entries();
        while (entries.hasMoreElements()) {
            final JarEntry jarEntry = entries.nextElement();
            System.out.println("Extracting file : " + jarEntry.getName());
            final File tmpFile = new File(joinFiles(uploadRootPath, jarEntry.getName()));
            if (jarEntry.isDirectory()) {
                if (!tmpFile.mkdir()) {
                    logError("Can't create directory: " + tmpFile.getName(), null);
                    return;
                }
            } else {
                extractEntryFile(jarFile, jarEntry, tmpFile);
            }
        }
    }

    private void extractEntryFile(JarFile jarFile, ZipEntry jarEntry, File tmpFile) {
        try {
            final InputStream in = jarFile.getInputStream(jarEntry);
            final FileOutputStream out = new FileOutputStream(tmpFile);

            int readedBytes = in.read();
            while (readedBytes != -1) {
                out.write(readedBytes);
                readedBytes = in.read();
            }

            out.close();
            in.close();
        } catch (FileNotFoundException e) {
            logError(e.getMessage(), e);
        } catch (IOException e) {
            logError(e.getMessage(), e);
        }
    }

/*    public void setUploadRoot(String uploadRootPath) throws IOException {
        if (uploadRootPath != null) {
            this.uploadRoot = new File(uploadRootPath);
            for (String f : uploadRoot.list()) {
                final File temp = new File(uploadRoot, f);
                temp.delete();
            }
            uploadRoot.delete();
            uploadRoot.mkdirs();
            this.uploadRootPath = this.uploadRoot.getCanonicalPath() + File.separator;
        } else {
            this.uploadRoot = null;
            this.uploadRootPath = null;
        }
    }*/


    private File getFileByPath(String path) {
        if (this.uploadRoot != null) {
            File result = new File(this.uploadRoot, path);

            try {
                final String resultCanonicalPath = result.getCanonicalPath();
                if (!resultCanonicalPath.startsWith(this.uploadRootPath)) {
                    // TODO log error
                    result = null;
                }
                return result;
            } catch (IOException e) {
                // TODO: handle exception
                result = null;
            }

            return result;
        }

        return null;
    }

    public boolean isDirectoryPresent(String directory) {
        final File file = getFileByPath(directory);
        return file.exists() && file.isDirectory();
    }

    public void deleteDirectory(String... directories) {
        final File file = getFileByPath(joinFiles(directories).toString());
        if (file.exists()) {
            for (String f : file.list()) {
                final File temp = new File(file, f);
                temp.delete();
            }
            file.delete();
        }
    }

    public boolean renameDirectory(String directoryOld, String directoryNew) {
        final File fileOld = getFileByPath(directoryOld);
        final File fileNew = getFileByPath(directoryNew);

        createDirectoryIfNotExist(directoryNew);
        if (fileNew.exists()) {
            if (fileNew.isDirectory()) {
                return false;
            } else {
                fileNew.delete();
            }
        }
        fileOld.renameTo(fileNew);
        return true;
    }

    public void addDirectory(String... directories) {
        final StringBuilder directory = new StringBuilder();
        for (String chunk : directories) {
            directory.append(chunk);
        }

        final File file = getFileByPath(directory.toString());
        file.mkdirs();
    }

    public boolean saveAvatar(String path, User user) {
		return writeFile("/" + user.getLogin() +
				"/avatar.jpg", path, "", 80, true);
	}

    public void renameImage(String fileNameOld, String fileNameNew) {
		createDirectoryIfNotExist(fileNameNew);
		renameImage(getFileByPath(fileNameNew), getFileByPath(fileNameOld));
		
		renameImage(getFileByPath(transformPath(fileNameNew, _MEDIUM)),
			getFileByPath(transformPath(fileNameOld, _MEDIUM)));
		
		renameImage(getFileByPath(transformPath(fileNameNew, _SMALL80)),
				getFileByPath(transformPath(fileNameOld, _SMALL80)));
		
		renameImage(getFileByPath(transformPath(fileNameNew, _SMALL120)),
				getFileByPath(transformPath(fileNameOld, _SMALL120)));
		
		renameImage(getFileByPath(transformPath(fileNameNew, _SMALL160)),
				getFileByPath(transformPath(fileNameOld, _SMALL160)));
		
		renameImage(getFileByPath(transformPath(fileNameNew, _SMALL200)),
				getFileByPath(transformPath(fileNameOld, _SMALL200)));
	}


	public void deleteImage(String fileName) {
		deleteImage(getFileByPath(fileName));
		deleteImage(getFileByPath(transformPath(fileName, _MEDIUM)));
		deleteImage(getFileByPath(transformPath(fileName, _SMALL80)));
		deleteImage(getFileByPath(transformPath(fileName, _SMALL120)));
		deleteImage(getFileByPath(transformPath(fileName, _SMALL160)));
		deleteImage(getFileByPath(transformPath(fileName, _SMALL200)));
	}

	public boolean addImage(String fileName, String  tempFilePath) {
		createDirectoryIfNotExist(fileName);
		if(!writeFile(fileName, tempFilePath, _SMALL80, 80, true)){
			return false;
		}
		if(!writeFile(fileName, tempFilePath, _SMALL120, 120, true)){
			return false;
		}
		if(!writeFile(fileName, tempFilePath, _SMALL160, 160, true)){
			return false;
		}
		if(!writeFile(fileName, tempFilePath, _SMALL200, 200, true)){
			return false;
		}
		if(!writeFile(fileName, tempFilePath, _MEDIUM, 600, true)){
			return false;
		}
		return true;
	}

    public static BufferedImage bitmapToImage(String data, String format) throws IOException {
        final InputStream inb = new FileInputStream(data);
        final ImageReader rdr = ImageIO.getImageReadersByFormatName(format).next();
        final ImageInputStream imageInput = ImageIO.createImageInputStream(inb);
        rdr.setInput(imageInput);
        final BufferedImage image = rdr.read(0);
        inb.close();
        return image;
    }

    public static void imageToBitmap(BufferedImage image, String data, String format) throws IOException {
        final OutputStream inb = new FileOutputStream(data);
        final ImageWriter wrt = ImageIO.getImageWritersByFormatName(format).next();
        final ImageInputStream imageInput = ImageIO.createImageOutputStream(inb);
        wrt.setOutput(imageInput);
        wrt.write(image);
        inb.close();
    }

    /**
     * Convenience method that returns a scaled instance of the
     * provided {@code BufferedImage}.
     *
     * @param img           the original image to be scaled
     * @param targetWidth   the desired width of the scaled instance,
     *                      in pixels
     * @param targetHeight  the desired height of the scaled instance,
     *                      in pixels
     * @param hint          one of the rendering hints that corresponds to
     *                      {@code RenderingHints.KEY_INTERPOLATION} (e.g.
     *                      {@code RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR},
     *                      {@code RenderingHints.VALUE_INTERPOLATION_BILINEAR},
     *                      {@code RenderingHints.VALUE_INTERPOLATION_BICUBIC})
     * @param higherQuality if true, this method will use a multi-step
     *                      scaling technique that provides higher quality than the usual
     *                      one-step technique (only useful in downscaling cases, where
     *                      {@code targetWidth} or {@code targetHeight} is
     *                      smaller than the original dimensions, and generally only when
     *                      the {@code BILINEAR} hint is specified)
     * @return a scaled version of the original {@code BufferedImage}
     */
    public static BufferedImage getScaledInstance(BufferedImage img,
                                                  int targetWidth,
                                                  int targetHeight,
                                                  Object hint,
                                                  boolean higherQuality) {
        final int type = img.getTransparency() == Transparency.OPAQUE ?
                BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB;
        BufferedImage ret = (BufferedImage) img;
        int w;
        int h;
        if (higherQuality) {
            // Use multi-step technique: start with original size, then
            // scale down in multiple passes with drawImage()
            // until the target size is reached
            w = img.getWidth();
            h = img.getHeight();
        } else {
            // Use one-step technique: scale directly from original
            // size to target size with a single drawImage() call
            w = targetWidth;
            h = targetHeight;
        }

        do {
            if (higherQuality && w > targetWidth) {
                w /= 2;
                if (w < targetWidth) {
                    w = targetWidth;
                }
            }

            if (higherQuality && h > targetHeight) {
                h /= 2;
                if (h < targetHeight) {
                    h = targetHeight;
                }
            }

            final BufferedImage tmp = new BufferedImage(w, h, type);
            final Graphics2D g2 = tmp.createGraphics();
            g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, hint);
            g2.drawImage(ret, 0, 0, w, h, null);
            g2.dispose();

            ret = tmp;
        } while (w != targetWidth || h != targetHeight);

        return ret;
    }

    public boolean writeFile(String newFileName, String fileName, String pattern, int size, boolean includeUploadRoot) {
        BufferedImage bsrc = null;
        try {
            bsrc = bitmapToImage(fileName, "JPG");
        } catch (IOException e1) {
            return false;
        }
        final int i = bsrc.getWidth() > bsrc.getHeight() ? bsrc.getWidth() : bsrc.getHeight();
        final double d = ((double) size) / i;
        final int width = (int) d * bsrc.getWidth();
        final int height = (int) d * bsrc.getHeight();
        final BufferedImage bdest = getScaledInstance(bsrc, width, height, RenderingHints.VALUE_INTERPOLATION_BICUBIC, true);
        final String dest = includeUploadRoot ? this.uploadRootPath + transformPath(newFileName, pattern) : transformPath(newFileName, pattern);
        try {
            imageToBitmap(bdest, dest, "JPG");
        } catch (IOException ex) {
        	return false;
        }
        return true;
    }

    public String transformPath(String target, String substitute) {
        final String begin = target.substring(0, target.lastIndexOf(Constants.DOT));
        final String end = target.substring(target.lastIndexOf(Constants.DOT));
        return begin + substitute + end;
    }

    private void deleteImage(File file) {
        if (file.exists()) {
            file.delete();
        }
    }

    public String getPathOfImage(String path) {
        final int i = path.lastIndexOf(Constants.SLASH);
        return path.substring(i);
    }

    private void renameImage(File fileNew, File fileOld) {
        if (fileNew.exists()) {
            fileNew.delete();
        }
        fileOld.renameTo(fileNew);
    }

    private void createDirectoryIfNotExist(String fileNameNew) {
        final int lastIndexOf = fileNameNew.lastIndexOf('/');
        if (lastIndexOf > 0) {
            final String directory = fileNameNew.substring(0, lastIndexOf);
            final File file = getFileByPath(directory);
            if (!file.exists()) {
                file.mkdirs();
            }
        }
    }

    public boolean isImagePresent(String fileName) {
        return getFileByPath(fileName) != null && getFileByPath(fileName).exists() && !getFileByPath(fileName).isDirectory();
    }

    public File getImage(String fileName) {
        if (isImagePresent(fileName)) {
            return getFileByPath(fileName);
        }
        return null;
    }

    public File getImageFile(String fileName) {
        return new File(fileName);
    }

    public void deleteDirectories(Iterable<String> directoriesToDelete) {
        for (String directory : directoriesToDelete) {
            deleteDirectory(directory);
        }
    }
}