package jp.sourceforge.shovel.entity.impl;

import static java.awt.Image.*;
import static jp.sourceforge.shovel.CommonConst.*;
import static jp.sourceforge.shovel.SizeType.*;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

import javax.imageio.ImageIO;
import javax.servlet.ServletContext;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.SystemUtils;
import org.seasar.dao.annotation.tiger.Bean;
import org.seasar.dao.annotation.tiger.Id;
import org.seasar.dao.annotation.tiger.IdType;
import org.seasar.framework.container.S2Container;
import org.seasar.framework.container.annotation.tiger.Binding;
import org.seasar.framework.container.annotation.tiger.BindingType;
import org.seasar.framework.container.factory.SingletonS2ContainerFactory;


import jp.sourceforge.shovel.SizeType;
import jp.sourceforge.shovel.entity.IMimeType;
import jp.sourceforge.shovel.entity.IServerFile;
import jp.sourceforge.shovel.entity.ISize;
import jp.sourceforge.shovel.exception.ApplicationException;
import jp.sourceforge.shovel.logic.IMimeTypeLogic;
import jp.sourceforge.shovel.logic.IServerFileLogic;
import jp.sourceforge.shovel.util.ServerFileUtil;

@Bean(table = "serverfiles")
public class ServerFileImpl implements IServerFile {
    String charset_;
    String mime_;
    String name_;
    long serverFileId_;
    int size_;
    long createdTime_;

    public ServerFileImpl() {
        full_ = (ISize)getContainer().getComponent(ISize.class);
        sizeMap_ = new HashMap<SizeType, ISize>();
    }
    public String getCharset() {
        return charset_;
    }
    public void setCharset(String charset) {
        charset_ = charset;
    }
    public String getMime() {
        return mime_;
    }
    public void setMime(String mime) {
        mime_ = mime;
    }
    public String getName() {
        return name_;
    }
    public void setName(String name) {
        name_ = name;
    }
    public long getServerFileId() {
        return serverFileId_;
    }
    @Id(value = IdType.IDENTITY)
    public void setServerFileId(long serverFileId) {
        serverFileId_ = serverFileId;
    }
    public int getSize() {
        return size_;
    }
    public void setSize(int size) {
        size_ = size;
    }
    public long getCreatedTime() {
        return createdTime_;
    }
    public void setCreatedTime(long createdTime) {
        createdTime_ = createdTime;
    }
    
    String contentType_;
    IServerFileLogic serverFileLogic_;
    File temp_;
    File[] thumbnail_;
    File temporaryThumbnail_;
    File file_ = null;
    
    ISize full_;
    Map<SizeType, ISize> sizeMap_;

    public String getContentType() {
        return contentType_;
    }
    public void setContentType( String contentType ) {
        contentType_ = contentType;
    }

    S2Container getContainer() {
        return SingletonS2ContainerFactory.getContainer();
    }

    void copy(InputStream inputStream, OutputStream outputStream) throws ApplicationException {
        ApplicationException e = null;
        try {
            int size;
            byte[] buffer = new byte[4096];
            while ((size = inputStream.read(buffer, 0, 4096)) > 0) {
                outputStream.write(buffer, 0, size);
            }
        } catch (IOException cause) {
            //TODO
            e = new ApplicationException("");
            e.initCause(cause);
            throw e;
        } finally {
            IOException cause1 = null;
            try {
                if (outputStream != null) {
                    outputStream.close();
                }
            } catch (IOException cause2) {
                cause1 = cause2;
            }
            try {
                if (inputStream != null) {
                    inputStream.close();
                }
            } catch (IOException cause2) {
                cause1 = cause1 == null ? cause2 : cause1;
            }
            if (e == null && cause1 != null) {
                //TODO
                e = new ApplicationException("");
                e.initCause(cause1);
                throw e;
            }
        }
    }
    public boolean isExists(SizeType sizeType) throws ApplicationException {
        if (sizeType.isOriginal()) {
            return false;
        }
        String path = ServerFileUtil.formatPath(null, getThumbnailDir(sizeType),
                getServerFileId(), true);
        File file = new File(path);
        return file.exists();
    }
    //TODO そのうちキューでまったり削除させる
    public void commit(int process) throws ApplicationException {
        switch (process) {
        case 0: //作成
            InputStream inputStream = null;
            OutputStream outputStream = null;
            ApplicationException e = null;
            try {
                if (thumbnail_ != null) {
                    for (SizeType sizeType : SizeType.values()) {
                        if (sizeType.isOriginal()) {
                            continue;
                        }
                        String path = ServerFileUtil.formatPath(null,
                                getThumbnailDir(sizeType), getServerFileId(), true);
                        FileUtils.copyFile(thumbnail_[sizeType.getIndex()], new File(path));
                    }
                }
                String fileName = ServerFileUtil.formatPath(null, getFileDir(), getServerFileId(), true);
                outputStream = new FileOutputStream(fileName);
                inputStream = getInputStream();
                copy(inputStream, outputStream);
            } catch (IOException cause) {
                for (SizeType sizeType : SizeType.values()) {
                    if (sizeType.isOriginal()) {
                        continue;
                    }
                    String path = ServerFileUtil.formatPath(null,
                            getThumbnailDir(sizeType), getServerFileId(), true);
                    File file = new File(path);
                    if (file.exists()) {
                        file.delete();
                    }
                }
                
                //TODO
                e = new ApplicationException("");
                e.initCause(cause);
                throw e;
            } finally {
                IOException cause1 = null;
                try {
                    if (inputStream != null) {
                        inputStream.close();
                    }
                } catch (IOException cause2) {
                    cause1 = cause2;
                }
                try {
                    if (outputStream != null) {
                        outputStream.close();
                    }
                } catch (IOException cause2) {
                    cause1 = cause1 == null ? cause2 : cause1;
                }
                if (e == null && cause1 != null) {
                    e = new ApplicationException("");
                    e.initCause(cause1);
                    throw e;
                }
            }
            break;
        case 1: //削除
            for (SizeType sizeType : SizeType.values()) {
                if (sizeType.isOriginal()) {
                    continue;
                }
                String path = ServerFileUtil.formatPath(null, getThumbnailDir(sizeType),
                        getServerFileId(), false);
                File file = new File(path);
                file.delete();
            }
            String path = ServerFileUtil.formatPath(null,
                    getFileDir(), getServerFileId(), false);
            File file = new File(path);
            file.delete();
            break;
        default:
            //TODO コミットの種類が不正
            throw new ApplicationException("");
        }
    }
    public void commit(SizeType sizeType) throws ApplicationException {
        if (sizeType.isOriginal()) {
            return;
        }
        try {
            String path = ServerFileUtil.formatPath(null,
                    getThumbnailDir(sizeType), getServerFileId(), true);
            FileUtils.copyFile(thumbnail_[sizeType.getIndex()], new File(path));
        } catch (IOException cause) {
            String path = ServerFileUtil.formatPath(null,
                    getThumbnailDir(sizeType), getServerFileId(), true);
            File serverFile = new File(path);
            if (serverFile.exists()) {
                serverFile.delete();
            }
            ApplicationException e = new ApplicationException("");
            e.initCause(cause);
            throw e;
        }
    }
    public void remove() throws Exception {
        long[] serverFileIds = {getServerFileId()};
        serverFileLogic_.removeServerFiles(serverFileIds);
    }
    public void download(OutputStream outputStream, SizeType sizeType) throws ApplicationException {
        ServerFileUtil.prepareDownload(this);
        InputStream inputStream = getInputStream(sizeType);
        copy(inputStream, outputStream);
        try {
            inputStream.close();
        } catch (IOException cause) {
            //TODO
            ApplicationException e = new ApplicationException("");
            e.initCause(cause);
            throw e;
        }
    }
    IMimeTypeLogic getMimeTypeLogic() {
        return (IMimeTypeLogic)getContainer().getComponent(IMimeTypeLogic.class);
    }
    //コミットする前に
    //MacOSはImageMagickがAppleScriptに対応してないからダメ？
    //http://developer.apple.com/jp/technotes/tn1168.html
    public void createThumbnail(SizeType sizeType) throws ApplicationException {
        if (sizeType.isOriginal()) {
            return;
        }
        try {
            IMimeType mime = getMimeTypeLogic().getMimeTypeByFileName(getName());
            if (getMime() == null) {
                //TODO
                throw new ApplicationException("");
            }
            String[] tokens = temp_.getName().split("\\.");
            StringBuilder path = null;
            if (tokens.length <= 1) {
                path = new StringBuilder(temp_.getPath());
                path.append(".");
                path.append(mime.getExtension());
            } else if (tokens[tokens.length - 1].compareToIgnoreCase(mime.getExtension()) != 0) {
                path = new StringBuilder(temp_.getParent());
                path.append(File.separator);
                for (int i = 0; i < tokens.length - 1; i++) {
                    path.append(tokens[i]);
                    if (i < tokens.length - 2) {
                        path.append(".");
                    }
                }
                path.append(mime.getExtension());
            }
            if (path != null && path.toString().compareTo(temp_.getPath()) != 0) {
                File tempFile = new File(path.toString());
                FileUtils.copyFile(temp_, tempFile);
                temp_ = tempFile;
            }
            String input = temp_.getPath();
            
            File dir = new File(System.getProperty("java.io.tmpdir"));
            File tempFile = File.createTempFile("", "_" + sizeType.getId(), dir);
            String raw = tempFile.getPath();
            String withExt = raw;
            int index = raw.lastIndexOf(".");
            if (index < 0) {
                withExt = raw;
            } else {
                withExt = raw.substring(0, index);
                withExt += ".jpeg";
            }
            //File dst = new File(withExt);
            tempFile.delete();
            
            //String raw = ServerFileUtil.formatPath(null,
            //getThumbnailDir(sizeType), getServerFileId(), true);
            
            //Windowsだけ空白が入ったパスも考慮してダブルクォートで括る
            //String withExt = raw + ".gif";
            String output = withExt;
            if (SystemUtils.IS_OS_WINDOWS) {
                input = String.format("\"%s\"", input);
                output = String.format("\"%s\"", withExt);
            }
            
            Properties props = (Properties)getContainer().getComponent(COMMON_PROPERTIES);
            String size = String.valueOf(sizeType.getPx());
            String[] cmd = {
                props.getProperty("imagemagick.bin"),
//                "Image Magick",
//                input + " -coalesce -resize " + size + "x" + size + " -deconstruct " + output
                input,
                "-coalesce",
                "-resize",
                size + "x" + size,
                "-deconstruct",
                output
            };
            //アスペクト比を維持しつつ、枠内に収まるサイズに縮小
            ProcessBuilder builder = new ProcessBuilder(cmd);
            if (SystemUtils.IS_OS_LINUX) {
                Map<String, String> env = builder.environment();
                env.put("LD_LIBRARY_PATH", props.getProperty("imagemagick.lib"));
            }
            Process proc = builder.start();
            
            proc.waitFor();
            int result = proc.exitValue();
            //あてにならない？
            if (result != 0) {
                //TODO 失敗。。。
                throw new ApplicationException("");
            }
            if (thumbnail_ == null) {
                thumbnail_ = new File[4];
            }
            thumbnail_[sizeType.getIndex()] = new File(raw);
            (new File(withExt)).renameTo(thumbnail_[sizeType.getIndex()]);
        } catch (InterruptedException cause) {
            ApplicationException e = new ApplicationException("");
            e.initCause(cause);
            throw e;
        } catch (IOException cause) {
            ApplicationException e = new ApplicationException("");
            e.initCause(cause);
            throw e;
        }
    }

    /**
     * サムネイルの作成 File.createTempFileで一時ファイルを作成<br/>
     * コミットでサムネイル保存先に移動させる
     * @throws ApplicationException ファイルの入出力エラーで発生する
     */
    public void createTemporaryThumbnail(SizeType sizeType) throws ApplicationException {
        if (sizeType.isOriginal()) {
            return;
        }
        try {
            BufferedImage src = ServerFileUtil.getBufferedImage(this);
            if (src == null) {
                return;
            }
            
            ISize size = (ISize)getContainer().getComponent(ISize.class);
            size.setWidth(src.getWidth());
            size.setHeight(src.getHeight());
            int px = sizeType.getPx();
            size = size.computeFitSize(px, px);
            int x = (px - size.getWidth()) / 2;
            int y = (px - size.getHeight()) / 2;
            
            //TODO アドホックだが、PNGでなる
            int imageType = src.getType();
            if (imageType == BufferedImage.TYPE_CUSTOM) {
                if (getMime().compareTo("image/png") == 0 || getMime().compareTo("image/x-png") == 0) {
                    //TODO え。。TYPE_INT_ARGBじゃだめなの？！
                    imageType = BufferedImage.TYPE_INT_RGB;
                } else {
                    //TODO
                    throw new ApplicationException("");
                }
            }
            BufferedImage memory = new BufferedImage(sizeType.getPx(), sizeType.getPx(), imageType);
            Graphics2D graphics = memory.createGraphics();
            graphics.setColor(Color.white);
            graphics.fillRect(0, 0, px, px);
            //平均画素法
            Image img = src.getScaledInstance(size.getWidth(), size.getHeight(), SCALE_AREA_AVERAGING);
            graphics.drawImage(img, x, y, null);
            /*
            graphics.setRenderingHint(KEY_ALPHA_INTERPOLATION, VALUE_ALPHA_INTERPOLATION_QUALITY);
            graphics.setRenderingHint(KEY_ANTIALIASING, VALUE_ANTIALIAS_ON);
            graphics.setRenderingHint(KEY_COLOR_RENDERING, VALUE_COLOR_RENDER_QUALITY);
            graphics.setRenderingHint(KEY_DITHERING, VALUE_DITHER_ENABLE);
            graphics.setRenderingHint(KEY_TEXT_ANTIALIASING, VALUE_TEXT_ANTIALIAS_ON);
            graphics.setRenderingHint(KEY_RENDERING, VALUE_RENDER_QUALITY);
            graphics.setRenderingHint(KEY_INTERPOLATION, VALUE_INTERPOLATION_BILINEAR);
            graphics.setRenderingHint(KEY_FRACTIONALMETRICS, VALUE_FRACTIONALMETRICS_ON);
            graphics.setRenderingHint(KEY_STROKE_CONTROL, VALUE_STROKE_NORMALIZE);
            graphics.drawImage(src, x, y, x + size.getWidth(), y + size.getHeight(), 0, 0, src.getWidth(), src.getHeight(), null);
            */
            if (thumbnail_ == null) {
                thumbnail_ = new File[4];
            }
            thumbnail_[sizeType.getIndex()] = File.createTempFile("tmp_thumb_", null);
            ImageIO.write(memory, "jpeg", thumbnail_[sizeType.getIndex()]);
        } catch( IOException e ) {
            //TODO
            throw new ApplicationException("");
        }
    }
    /**
     * 入力ストリームは読み捨てが基本（らしい）
     */
    public InputStream getInputStream() throws ApplicationException {
        return getInputStream(null);
    }
    InputStream getInputStream(SizeType sizeType) throws ApplicationException {
        InputStream inputStream = null;
        try {
            if (temp_ == null) {
                String baseDir;
                if (sizeType == null || sizeType.isOriginal()) {
                    baseDir = getFileDir();
                } else {
                    baseDir = getThumbnailDir(sizeType);
                }
                String name = ServerFileUtil.formatPath(null, baseDir, getServerFileId(), false);
                inputStream = new FileInputStream(name);
            } else {
                inputStream = new FileInputStream(temp_);
            }
        } catch (IOException cause1) {
            try {
                if (inputStream != null) {
                    inputStream.close();
                    inputStream = null;
                }
            } catch (IOException cause2) {
                cause1 = cause2;
            }
            ApplicationException e = new ApplicationException("");
            e.initCause(cause1);
            throw e;
        }
        return inputStream;
    }
    @Binding(bindingType=BindingType.NONE)
    public void setInputStream(InputStream inputStream) throws ApplicationException {
        FileOutputStream outputStream = null;
        try {
            temp_ = File.createTempFile("temp_", null);
            outputStream = new FileOutputStream(temp_);
            copy(inputStream, outputStream);
        } catch (IOException cause) {
            ApplicationException e = new ApplicationException("");
            e.initCause(cause);
            throw e;
        }
    }
    public boolean isImage() {
        return getWidth() > 0 && getHeight() > 0;
    }
    public void prepare() throws ApplicationException {
        BufferedImage image = ServerFileUtil.getBufferedImage(this);
        if (image == null) {
            return;
        }
        setWidth(image.getWidth());
        setHeight(image.getHeight());
    }
    public void prepareForView() {
        for (SizeType sizeType : SizeType.values()) {
            int x = sizeType.getPx();
            int y = x;
            if (sizeType.isOriginal()) {
                y = -1;
            }
            ISize size = full_.computeFitSize(x, y);
            if (size.getWidth() > full_.getWidth()) {
                size = full_;
            }
            sizeMap_.put(sizeType, size);
        }
    }
    public int getWidth() {
        return full_.getWidth();
    }
    public void setWidth(int width) {
        full_.setWidth(width);
    }
    public int getHeight() {
        return full_.getHeight();
    }
    public void setHeight(int height) {
        full_.setHeight(height);
    }
    public ISize getOriginal() {
        return sizeMap_.get(ORIGINAL);
    }
    public ISize getSmall() {
        return sizeMap_.get(SMALL);
    }
    public ISize getMiddle() {
        return sizeMap_.get(MIDDLE);
    }
    public ISize getLarge() {
        return sizeMap_.get(LARGE);
    }
    public ISize getXlarge() {
        return sizeMap_.get(X_LARGE);
    }
    public boolean isExists() {
        String name = getName();
        return name != null && name.length() > 0;
    }
    String getFileDir() {
        return "serverFile";
    }
    String getThumbnailDir(SizeType sizeType) {
        if (sizeType.isOriginal()) {
            return null;
        }
        StringBuilder dir = new StringBuilder();
        dir.append("thumbnail");
        dir.append(File.separator);
        dir.append(String.format("%1$s", sizeType.getId()));
        return dir.toString();
    }
    public Object clone() {
        return null;
    }
    ServletContext application_;
    public void setApplication(ServletContext application) {
        application_ = application;
    }
    String getRealPath(String filename) {
        StringBuilder virtualPath = new StringBuilder();
        virtualPath.append("/shovel/image/");
        virtualPath.append(filename);
        return application_.getRealPath(virtualPath.toString());
    }
}
