package org.apache.commons.jelly.tags.io;

import org.apache.commons.jelly.TagSupport;
import org.apache.commons.jelly.XMLOutput;
import org.apache.commons.jelly.JellyException;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.logging.Log;

import java.io.*;

/**
 * Basic tag for copying files, moving files, renaming(moving) files, deleting files, and making directories.
 *
 * @author   Jason Horman (jason@jhorman.org)
 * @version  $Id$
 */

public class FileTag extends TagSupport {
    private Log logger = LogFactory.getLog(FileTag.class);

    /*********************************************************************
     *  Class variables
     *********************************************************************/
    public static final int MOVE = 0;
    public static final int COPY = 1;
    public static final int RENAME = 2;
    public static final int DELETE = 3;
    public static final int MKDIR = 4;

    /*********************************************************************
     *  Instance variables
     *********************************************************************/
    private int mode = -1;
    private File file = null;
    private File toFile = null;
    private boolean overwrite = false;

    /*********************************************************************
     *  Instance methods
     *********************************************************************/

    /**
     * Set the mode to one of the constants. Options are
     * "MOVE|COPY|RENAME|DELETE|MKDIR"
     */
    public void setOp(int mode) throws JellyException {
        if (mode >= MOVE && mode <= MKDIR) {
            this.mode = mode;
        } else {
            throw new JellyException("invalid mode");
        }
    }

    /**
     * Set the mode to "MOVE|COPY|RENAME|DELETE|MKDIR". The string
     * is case in-sensitive.
     */
    public void setOp(String mode) throws JellyException {
        String modeUpper = mode.toUpperCase();
        if (modeUpper.equals("MOVE")) {
            this.mode = MOVE;
        } else if (modeUpper.equals("COPY")) {
            this.mode = COPY;
        } else if (modeUpper.equals("RENAME")) {
            this.mode = RENAME;
        } else if (modeUpper.equals("DELETE")) {
            this.mode = DELETE;
        } else if (modeUpper.equals("MKDIR")) {
            this.mode = MKDIR;
        } else {
            throw new JellyException("invalid mode: " + mode);
        }
    }

    /**
     * Set the file to operate on
     */
    public void setFile(File file) {
        this.file = file;
    }

    /**
     * Really a synonym for setFile
     */
    public void setDir(File dir) {
        this.file = dir;
    }

    /**
     * For move or copy set the new file name
     */
    public void setToFile(File toFile) {
        this.toFile = toFile.getAbsoluteFile();
    }

    /**
     * Really a synonym for setToFile
     */
    public void setToDir(File toDir) {
        this.toFile = toDir.getAbsoluteFile();
    }

    /**
     * If you want the move or copy to overwrite existing files
     * set this to "true". The default is "false".
     */
    public void setOverwrite(boolean overwrite) {
        this.overwrite = overwrite;
    }

    /**
     * Execute the tag
     */
    public void doTag(XMLOutput xmlOutput) throws Exception {
        if (mode == -1) {
            throw new JellyException("no operations specified, call setOp()");
        }

        switch (mode) {
            case MOVE:
                logger.info("moving " + file + " to " + toFile);
                moveFile();
                break;
            case COPY:
                logger.info("copying " + file + " to " + toFile);
                copyFile();
                break;
            case DELETE:
                logger.info("deleting " + file);
                file.delete();
                break;
            case RENAME:
                logger.info("renaming " + file + " to " + toFile);
                moveFile();
                break;
            case MKDIR:
                logger.info("creating dir " + file);
                createDirectory();
                break;
        }
    }

    /**
     * Move the file, essentially just rename it.
     */
    public void moveFile() throws IOException {
        // if the to is a dir then set the to to the dir + the filename
        if (toFile.isDirectory()) {
            toFile = new File(toFile, file.getName());
        }

        // if the to exists we may want to delete it
        if (toFile.exists()) {
            if (overwrite) {
                logger.warn("deleting file " + toFile);
                toFile.delete();
            } else {
                throw new IOException("file " + toFile + " already exists");
            }
        }

        // create the parent directories for the move
        File parent = toFile.getParentFile();
        if (parent != null) {
            parent.mkdirs();
        }

        // rename the file
        file.renameTo(toFile);
    }

    public void copyFile() throws IOException {
        // if the to is a dir then set the to to the dir + the filename
        if (toFile.isDirectory()) {
            toFile = new File(toFile, file.getName());
        }

        // if the to exists we may want to delete it
        if (toFile.exists()) {
            if (overwrite) {
                toFile.delete();
            } else {
                throw new IOException("file " + toFile + " already exists");
            }
        }

        // create the parent directories for the move
        File parent = toFile.getParentFile();
        if (parent != null) {
            parent.mkdirs();
        }

        // copy the file
        InputStream in = null;
        OutputStream out = null;

        try {
            in = new BufferedInputStream(new FileInputStream(file));
            out = new BufferedOutputStream(new FileOutputStream(toFile));

            // copy the file kbyte by kbyte
            byte[] buf = new byte[1024];
            int bytesRead = 0;
            while ((bytesRead = in.read(buf)) != -1) {
                out.write(buf, 0, bytesRead);
            }
        } finally {
            try { if (in != null) in.close(); } catch (Exception e) {}
            try { if (out != null) out.close(); } catch(Exception e) {}
        }
    }

    /**
     * Create a new directory
     */
    public void createDirectory() {
        file.mkdirs();
    }
}
