/**
 * RenameWand 2.2
 * Copyright 2007 Zach Scrivena
 * 2007-12-09
 * zachscrivena@gmail.com
 * http://renamewand.sourceforge.net/
 *
 * RenameWand is a simple command-line utility for renaming files or
 * directories using an intuitive but powerful syntax.
 *
 * TERMS AND CONDITIONS:
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package renamewand;

import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.Random;


/**
 * Represent a (candidate) matched file/directory.
 */
class FileUnit
{
    /** source file/directory (absolute and canonical abstract pathname) */
    File source = null;

    /** parent directory identifier */
    int parentDirId = -1;

    /** global file/directory count */
    int globalCount = -1;

    /** local file/directory count */
    int localCount = -1;

    /** register values */
    String[] registerValues = null;

    /** target file/directory name */
    StringBuilder targetFilename = null;

    /** target file/directory (absolute and canonical abstract pathname) */
    File target = null;


    /**
    * Evaluate the specified macro, by returning the value of the specified
    * macro or assigned register corresponding to this file/directory.
    *
    * @param macro
    *     Macro or register name
    * @return
    *     String representation of the macro or assigned register value;
    *     null if macro or register is invalid or unassigned
    */
    String evaluateMacro(
            final String macro)
    {
        /************************
        * (1) FILE NAME MACROS *
        ************************/

        /* Example: "C:\Work\2007\Jan\Report.txt", with "C:\Work" as current directory */

        /* Filename ("Report.txt") */
        if ("FN".equals(macro))
        {
            String name = this.source.getName();

            /* trim off trailing separator */
            while (name.endsWith(File.separator))
                name = name.substring(0, name.length() - File.separator.length());

            return name;
        }

        /* File extension ("txt") */
        if ("FN.ext".equals(macro))
        {
            String name = this.source.getName();

            /* trim off trailing separator */
            while (name.endsWith(File.separator))
                name = name.substring(0, name.length() - File.separator.length());

            final int i = name.lastIndexOf(".");

            if (i < 0)
            {
                return "";
            }
            else
            {
                return name.substring(i + 1);
            }
        }

        /* Base filename without extension ("Report") */
        if ("FN.name".equals(macro))
        {
            String name = this.source.getName();

            /* trim off trailing separator */
            while (name.endsWith(File.separator))
                name = name.substring(0, name.length() - File.separator.length());

            final int i = name.lastIndexOf(".");

            if (i < 0)
            {
                return name;
            }
            else
            {
                return name.substring(0, i);
            }
        }

        /* Relative pathname ("2007\Jan\Report.txt") */
        if ("FN.path".equals(macro))
        {
            String path = this.source.getPath();

            if (path.startsWith(RenameWand.currentDirectoryFullPathname))
                path = path.substring(RenameWand.currentDirectoryFullPathnameLength);

            /* trim off trailing separator */
            while (path.endsWith(File.separator))
                path = path.substring(0, path.length() - File.separator.length());

            return path;
        }

        /* Parent directory ("Jan") */
        if ("FN.parent".equals(macro))
        {
            final File parent = this.source.getParentFile();

            if ((parent == null) || (parent.equals(RenameWand.currentDirectory)))
            {
                return "";
            }
            else
            {
                String name = parent.getName();

                /* trim off trailing separator */
                while (name.endsWith(File.separator))
                    name = name.substring(0, name.length() - File.separator.length());

                return name;
            }
        }

        /* Relative pathname of parent directory ("2007\Jan") */
        if ("FN.parentpath".equals(macro))
        {
            final File parent = this.source.getParentFile();

            if ((parent == null) || (parent.equals(RenameWand.currentDirectory)))
            {
                return "";
            }
            else
            {
                String path = parent.getPath();

                if (path.startsWith(RenameWand.currentDirectoryFullPathname))
                    path = path.substring(RenameWand.currentDirectoryFullPathnameLength);

                /* trim off trailing separator */
                while (path.endsWith(File.separator))
                    path = path.substring(0, path.length() - File.separator.length());

                return path;
            }
        }


        /************************
        * (2) FILE SIZE MACROS *
        ************************/

        /* all values are cast as integers */

        /* File size in bytes */
        if ("FS".equals(macro))
            return this.source.length() + "";

        /* File size in kilobytes (2^10 bytes) */
        if ("FS.kB".equals(macro))
            return (this.source.length() / 1024L) + "";

        /* File size in megabytes (2^20 bytes) */
        if ("FS.MB".equals(macro))
            return (this.source.length() / 1048576L) + "";

        /* File size in gigabytes (2^30 bytes) */
        if ("FS.GB".equals(macro))
            return (this.source.length() / 1073741824L) + "";

        /* File size in terabytes (2^40 bytes) */
        if ("FS.TB".equals(macro))
            return (this.source.length() / 1099511627776L) + "";


        /**************************************
        * (3) FILE LAST-MODIFIED TIME MACROS *
        **************************************/

        /* Number of milliseconds since the epoch (January 1, 1970 00:00:00 GMT, Gregorian) */
        if ("FT".equals(macro))
            return this.source.lastModified() + "";

        /* Date in the form yyyyMMdd */
        if ("FT.date".equals(macro))
            return (new SimpleDateFormat("yyyyMMdd", Locale.ENGLISH)).format(new Date(this.source.lastModified()));

        /* Time in the form HHmmss */
        if ("FT.time".equals(macro))
            return (new SimpleDateFormat("HHmmss", Locale.ENGLISH)).format(new Date(this.source.lastModified()));

        /* am/pm in lower case */
        if ("FT.ap".equals(macro))
            return (new SimpleDateFormat("a", Locale.ENGLISH)).format(new Date(this.source.lastModified())).toLowerCase(Locale.ENGLISH);

        /* AM/PM in upper case */
        if ("FT.AP".equals(macro))
            return (new SimpleDateFormat("a", Locale.ENGLISH)).format(new Date(this.source.lastModified())).toUpperCase(Locale.ENGLISH);

        /* Date and time pattern letters from Java */
        if (macro.startsWith("FT."))
        {
            final String macroName = macro.substring(macro.indexOf(".") + 1);

            if (macroName.length() > 0)
            {
                boolean validChars = true;

                for (int i = 0; i < macroName.length(); i++)
                {
                    if (!"GyMwWDdFEaHkKhmsSzZ".contains(macroName.charAt(i) + ""))
                    {
                        validChars = false;
                        break;
                    }
                }

                if (validChars)
                    return (new SimpleDateFormat(macroName, Locale.ENGLISH)).format(new Date(this.source.lastModified()));
            }
        }


        /***************************
        * (4) CURRENT TIME MACROS *
        ***************************/

        /* Number of milliseconds since the epoch (January 1, 1970 00:00:00 GMT, Gregorian) */
        if ("CT".equals(macro))
            return (new Date()).getTime() + "";

        /* Date in the form yyyyMMdd */
        if ("CT.date".equals(macro))
            return (new SimpleDateFormat("yyyyMMdd", Locale.ENGLISH)).format(new Date());

        /* Time in the form HHmmss */
        if ("CT.time".equals(macro))
            return (new SimpleDateFormat("HHmmss", Locale.ENGLISH)).format(new Date());

        /* am/pm in lower case */
        if ("CT.ap".equals(macro))
            return (new SimpleDateFormat("a", Locale.ENGLISH)).format(new Date()).toLowerCase(Locale.ENGLISH);

        /* AM/PM in upper case */
        if ("CT.AP".equals(macro))
            return (new SimpleDateFormat("a", Locale.ENGLISH)).format(new Date()).toUpperCase(Locale.ENGLISH);

        /* Date and time pattern letters from Java */
        if (macro.startsWith("CT."))
        {
            final String macroName = macro.substring(macro.indexOf(".") + 1);

            if (macroName.length() > 0)
            {
                boolean validChars = true;

                for (int i = 0; i < macroName.length(); i++)
                {
                    if (!"GyMwWDdFEaHkKhmsSzZ".contains(macroName.charAt(i) + ""))
                    {
                        validChars = false;
                        break;
                    }
                }

                if (validChars)
                    return (new SimpleDateFormat(macroName, Locale.ENGLISH)).format(new Date());
            }
        }


        /*******************************************
        * (5) SYSTEM ENVIRONMENT VARIABLES MACROS *
        *******************************************/

        if (macro.startsWith("ENV."))
        {
            final String macroName = macro.substring(macro.indexOf(".") + 1);

            if (macroName.length() > 0)
            {
                final String value = System.getenv(macroName);

                if (value != null)
                    return value;
            }
        }


        /********************************
        * (6) SYSTEM PROPERTIES MACROS *
        ********************************/

        if (macro.startsWith("SYS."))
        {
            final String macroName = macro.substring(macro.indexOf(".") + 1);

            if (macroName.length() > 0)
            {
                final String value = System.getProperty(macroName);

                if (value != null)
                    return value;
            }
        }


        /****************************
        * (7) MISCELLANEOUS MACROS *
        ****************************/

        /* Full pathname of the current directory (e.g. "C:\Work") */
        if ("RW.cd".equals(macro))
        {
            String path = RenameWand.currentDirectoryFullPathname;

            /* trim off trailing separator */
            while (path.endsWith(File.separator))
                path = path.substring(0, path.length() - File.separator.length());

            return path;
        }

        /* Number of local matched files (i.e. in the file's subdirectory) */
        if ("RW.N".equals(macro) && (this.localCount > 0))
            return this.localCount + "";

        /* Number of global matched files (i.e. in all subdirectories) */
        if ("RW.NN".equals(macro) && (this.globalCount > 0))
            return this.globalCount + "";

        /* Generate a string of 10 random digits */
        if ("RW.random".equals(macro))
        {
            /* return a string of 10 random digits (0-9) */
            final Random rng = new Random();
            final StringBuilder randomString = new StringBuilder();

            /* append a digit */
            while (randomString.length() < 10)
                randomString.append(rng.nextInt(10));

            return randomString.substring(0, 10);
        }


        /***********************************************
        * (8) MACRO/REGISTER MODIFIERS AND ATTRIBUTES *
        ***********************************************/

        /* Example: "hello WORLD" */

        /* Length of the string (11) */
        if (macro.endsWith(".len"))
        {
            final String parentMacro = macro.substring(0, macro.lastIndexOf("."));
            final String parentMacroValue = this.evaluateMacro(parentMacro);

            if (parentMacroValue != null)
                return parentMacroValue.length() + "";
        }

        /* Convert to upper case ("HELLO WORLD") */
        if (macro.endsWith(".upper"))
        {
            final String parentMacro = macro.substring(0, macro.lastIndexOf("."));
            final String parentMacroValue = this.evaluateMacro(parentMacro);

            if (parentMacroValue != null)
                return parentMacroValue.toUpperCase();
        }

        /* Convert to lower case ("hello world") */
        if (macro.endsWith(".lower"))
        {
            final String parentMacro = macro.substring(0, macro.lastIndexOf("."));
            final String parentMacroValue = this.evaluateMacro(parentMacro);

            if (parentMacroValue != null)
                return parentMacroValue.toLowerCase();
        }

        /* Capitalize only the first character ("Hello world") */
        if (macro.endsWith(".capitalize"))
        {
            final String parentMacro = macro.substring(0, macro.lastIndexOf("."));
            final String parentMacroValue = this.evaluateMacro(parentMacro);

            if (parentMacroValue != null)
            {
                if (parentMacroValue.isEmpty())
                    return parentMacroValue;

                return Character.toUpperCase(parentMacroValue.charAt(0)) +
                        parentMacroValue.substring(1).toLowerCase();
            }
        }

        /* Convert to title case ("Hello World") */
        if (macro.endsWith(".title"))
        {
            final String parentMacro = macro.substring(0, macro.lastIndexOf("."));
            final String parentMacroValue = this.evaluateMacro(parentMacro);

            if (parentMacroValue != null)
                return StringManipulator.toTitleCase(parentMacroValue);
        }

        /* Convert to camelCase ("helloWorld") */
        if (macro.endsWith(".camel"))
        {
            final String parentMacro = macro.substring(0, macro.lastIndexOf("."));
            final String parentMacroValue = this.evaluateMacro(parentMacro);

            if (parentMacroValue != null)
            {
                final String value = StringManipulator.deleteWhitespace(StringManipulator.toTitleCase(parentMacroValue));

                if (value.isEmpty())
                    return value;

                return Character.toLowerCase(value.charAt(0)) + value.substring(1);
            }
        }

        /* Convert to PascalCase ("HelloWorld") */
        if (macro.endsWith(".pascal"))
        {
            final String parentMacro = macro.substring(0, macro.lastIndexOf("."));
            final String parentMacroValue = this.evaluateMacro(parentMacro);

            if (parentMacroValue != null)
                return StringManipulator.deleteWhitespace(StringManipulator.toTitleCase(parentMacroValue));
        }

        /* Swap the case ("HELLO world") */
        if (macro.endsWith(".swapcase"))
        {
            final String parentMacro = macro.substring(0, macro.lastIndexOf("."));
            final String parentMacroValue = this.evaluateMacro(parentMacro);

            if (parentMacroValue != null)
                return StringManipulator.swapCase(parentMacroValue);
        }

        /* Abbreviate to initials ("h W") */
        if (macro.endsWith(".abbrev"))
        {
            final String parentMacro = macro.substring(0, macro.lastIndexOf("."));
            final String parentMacroValue = this.evaluateMacro(parentMacro);

            if (parentMacroValue != null)
                return StringManipulator.abbreviate(parentMacroValue);
        }

        /* Reverse the string ("DLROW olleh") */
        if (macro.endsWith(".reverse"))
        {
            final String parentMacro = macro.substring(0, macro.lastIndexOf("."));
            final String parentMacroValue = this.evaluateMacro(parentMacro);

            if (parentMacroValue != null)
                return StringManipulator.reverse(parentMacroValue);
        }

        /* Trim away whitespace on the left and right */
        if (macro.endsWith(".trim"))
        {
            final String parentMacro = macro.substring(0, macro.lastIndexOf("."));
            final String parentMacroValue = this.evaluateMacro(parentMacro);

            if (parentMacroValue != null)
                return parentMacroValue.trim();
        }

        /* Trim away whitespace on the left */
        if (macro.endsWith(".ltrim"))
        {
            final String parentMacro = macro.substring(0, macro.lastIndexOf("."));
            final String parentMacroValue = this.evaluateMacro(parentMacro);

            if (parentMacroValue != null)
                return StringManipulator.leftTrim(parentMacroValue);
        }

        /* Trim away whitespace on the right */
        if (macro.endsWith(".rtrim"))
        {
            final String parentMacro = macro.substring(0, macro.lastIndexOf("."));
            final String parentMacroValue = this.evaluateMacro(parentMacro);

            if (parentMacroValue != null)
                return StringManipulator.rightTrim(parentMacroValue);
        }

        /* Delete whitespace in the string */
        if (macro.endsWith(".delspace"))
        {
            final String parentMacro = macro.substring(0, macro.lastIndexOf("."));
            final String parentMacroValue = this.evaluateMacro(parentMacro);

            if (parentMacroValue != null)
                return StringManipulator.deleteWhitespace(parentMacroValue);
        }

        /* Delete extra whitespace by replacing contiguous whitespace with a single space */
        if (macro.endsWith(".delextraspace"))
        {
            final String parentMacro = macro.substring(0, macro.lastIndexOf("."));
            final String parentMacroValue = this.evaluateMacro(parentMacro);

            if (parentMacroValue != null)
                return StringManipulator.deleteExtraWhitespace(parentMacroValue);
        }

        /* Delete punctuation marks in the string */
        if (macro.endsWith(".delpunctuation"))
        {
            final String parentMacro = macro.substring(0, macro.lastIndexOf("."));
            final String parentMacroValue = this.evaluateMacro(parentMacro);

            if (parentMacroValue != null)
                return StringManipulator.deletePunctuation(parentMacroValue);
        }

        /* Space out words by inserting a space between connected words */
        if (macro.endsWith(".spaceout"))
        {
            final String parentMacro = macro.substring(0, macro.lastIndexOf("."));
            final String parentMacroValue = this.evaluateMacro(parentMacro);

            if (parentMacroValue != null)
                return StringManipulator.spaceOutWords(parentMacroValue);
        }


        /****************************
        * (9) REGISTERS (ASSIGNED) *
        ****************************/

        /* MUST BE THIRD-LAST BLOCK */

        if ((this.registerValues != null) &&
                RenameWand.registerNames.containsKey(macro))
        {
            return this.registerValues[RenameWand.registerNames.get(macro)];
        }


        /***************************************************
        * (10) SHORTCUTS FOR SINGLE-LETTER REGISTER NAMES *
        ***************************************************/

        /* MUST BE SECOND-LAST BLOCK */

        /* Example: a ---> aa AA Aa aA
        * Rule: If name is not a register, and is two chars long,
        * and first char == second char (ignore case), and exclusively
        * either small or big letter is a register name, then return
        * .lower, .upper, .title, .swapcase
        */
        if ((this.registerValues != null) &&
                (macro.length() == 2))
        {
            final String c1 = macro.charAt(0) + "";
            final String c2 = macro.charAt(1) + "";

            if (c1.equalsIgnoreCase(c2))
            {
                final String upper = c1.toUpperCase(Locale.ENGLISH);
                final String lower = c1.toLowerCase(Locale.ENGLISH);
                final boolean upperIsRegister = RenameWand.registerNames.containsKey(upper);
                final boolean lowerIsRegister = RenameWand.registerNames.containsKey(lower);
                String parentMacro = null;

                if (upperIsRegister && !lowerIsRegister)
                {
                    parentMacro = upper;
                }
                else if (lowerIsRegister && !upperIsRegister)
                {
                    parentMacro = lower;
                }

                if (parentMacro != null)
                {
                    if (macro.equals(lower + lower))
                        return this.evaluateMacro(parentMacro + ".lower");

                    if (macro.equals(upper + upper))
                        return this.evaluateMacro(parentMacro + ".upper");

                    if (macro.equals(upper + lower))
                        return this.evaluateMacro(parentMacro + ".title");

                    if (macro.equals(lower + upper))
                        return this.evaluateMacro(parentMacro + ".swapcase");
                }
            }
        }


        /***************************
        * (11) UNIDENTIFIED MACRO *
        ***************************/

        /* MUST BE LAST BLOCK */

        return null; // this should be the only "return null" in this method
    }
}