 /*
  * RHQ Management Platform
  * Copyright (C) 2005-2008 Red Hat, Inc.
  * All rights reserved.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2, as
  * published by the Free Software Foundation, and/or the GNU Lesser
  * General Public License, version 2.1, also as published by the Free
  * Software Foundation.
  *
  * 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 and the GNU Lesser General Public License
  * for more details.
  *
  * You should have received a copy of the GNU General Public License
  * and the GNU Lesser General Public License along with this program;
  * if not, write to the Free Software Foundation, Inc.,
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  */
package org.rhq.core.pluginapi.util;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;

/**
 * A set of utilities for working with files and file paths.
 *
 * @author Ian Springer
 */
public abstract class FileUtils {

    /**
     * The purpose of this method is to purge a directory of all of its contents, but it can also
     * be used to simply delete a given file.  If <code>dir</code> is a directory, this method will
     * attempt to delete all of its files and all of its subdirectories and their child files/subdirectories.
     * <code>dir</code> itself will then be deleted, but only if <code>deleteIt</code> is <code>true</code>.
     * If <code>dir</code> is not a directory, but rather a simple file, it will be deleted only if
     * <code>deleteIt</code> is <code>true</code>.
     * 
     * Note - This method does not protect against symbolic links and will follow them on UNIX/Linux.
     * 
     * <p>If <code>dir</code> is <code>null</code>, this method does nothing.</p>
     * 
     * @param fileOrDir the file or directory to purge
     * @param deleteIt if <code>true</code>, <code>dir</code> will be deleted after all of its contents are
     *                 deleted
     * @throws IOException if the purge fails
     */
    public static void purge(File fileOrDir, boolean deleteIt) throws IOException {
        if (fileOrDir != null) {
            if (fileOrDir.isDirectory()) {
                File[] doomedFiles = fileOrDir.listFiles();
                if (doomedFiles != null) {
                    for (File doomedFile : doomedFiles) {
                        purge(doomedFile, true); // recurse
                    }
                }
            }
            if (deleteIt) {
                if (!fileOrDir.delete())
                    throw new IOException("Failed to delete file or directory: " + fileOrDir);
            } else {
                if (fileOrDir.isDirectory() && fileOrDir.list().length != 0)
                    throw new IOException("Failed to delete contents of directory: " + fileOrDir);
            }
        }
    }

    // I *think* this returns the entire line where stringToFind is found
    public static String findString(String filePath, String stringToFind) throws IOException {
        StringBuilder result = null;

        BufferedReader in = new BufferedReader(new FileReader(filePath));

        try {
            char[] data = new char[8096];

            int numread;
            int toFindIndex = 0;
            /* Just need to initialize this, because the compiler doesn't
             * realize that it can't be used before it is assigned a value
             */
            char lastchar = 'a';
            while ((numread = in.read(data, 0, 8096)) != -1) {
                for (int i = 0; i < numread; i++) {
                    /* If we have found the string already or if we our current
                     * character matches the current char in the target string
                     * then we just add the current character to our result
                     * string and move on.
                     */
                    if (toFindIndex >= stringToFind.length() || data[i] == stringToFind.charAt(toFindIndex)) {
                        if (result == null) {
                            result = new StringBuilder();
                        }
                        if (Character.isISOControl(data[i])) {
                            return result.toString();
                        }
                        result.append(data[i]);
                        toFindIndex++;
                    } else {
                        /* Otherwise things can get complex.  If we haven't
                         * started to match, then just keep going.  If we have
                         * started to match, then we need to move backwards
                         * to make sure we don't miss a match.  For example:
                         * looking for HI in HHI.  If the current character
                         * isn't the same as the last character, then we aren't
                         * going to match, so null everything out and keep
                         * going.  Otherwise, decrment everything by one,
                         * because we didn't match the first character, and
                         * go through the loop on this character again.
                         */
                        if (toFindIndex > 0) {
                            if (data[i] != lastchar) {
                                result = null;
                                toFindIndex = 0;
                                continue;
                            }
                            toFindIndex--;
                            i--;
                            result.deleteCharAt(result.length() - 1);
                            continue;
                        }
                    }
                    lastchar = data[i];
                }
            }
        } catch (IOException e) {
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (Exception e) {
                }
            }
        }

        if (result != null) {
            return result.toString();
        }

        return null;
    }

    /**
     * Canonicalize the specified file path according to the current platform's rules:
     * <ul>
     *   <li>Condense multiple consecutive path separators into a single path separator.</li>
     *   <li>Remove "." path elements.</li>
     *   <li>Resolve ".." path elements.</li>
     *   <li>On Windows, normalize capitalization.</li>
     *   <li>On Windows, expand 8.3-abbreviated path elements (e.g. "DOCUME~1" -> "Documents and Settings").</li>
     * </ul>
     * <p/>
     * Unlike {@link File#getCanonicalPath()}, this method does <b>not</b> resolve symlinks.</li>
     * <p/>
     * The path may or may not reference an existing file.
     *
     * @param path the file path to be canonicalized
     *
     * @return the canonicalized file path
     */
    public static String getCanonicalPath(String path) {
        File file = new File(path);
        if (isUnix()) {
            // UNIX - Do *not* use File#getCanonicalFile, since it will resolve symlinks.
            file = new File(file.toURI().normalize()).getAbsoluteFile();
        } else {
            // Windows - Use File#getCanonicalFile(), since it will normalize
            // capitalization and will not resolve junctions (i.e. the NTFS
            // equivalent of symlinks).
            try {
                file = file.getCanonicalFile();
            } catch (IOException e) {
                // best we can do...
                file = new File(file.toURI().normalize()).getAbsoluteFile();
            }
        }
        return file.getPath();
    }

    private FileUtils() {
    }

    private static boolean isUnix() {
        return File.separatorChar == '/';
    }

}
