// $Id: Utility.java,v 1.16 2004/08/23 15:55:30 m31 Exp $
//
// This file is part of the program suite "Principia Mathematica II"
// which is a working prototype for the main project:
// "Hilbert II" - http://www.qedeq.org
//
// Copyright 2001-2004  Michael Meyling <mime@qedeq.org>.
//
// "Principia Mathematica II" 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 2 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.

package com.meyling.principia.io;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.Properties;

import com.meyling.principia.log.Trace;


/**
 * A collection of usefull static methods.
 *
 * @version $Revision: 1.16 $
 * @author  Michael Meyling
 */
public class Utility {


    /**
     * Constructor, should never be called.
     */
    private Utility() {
    }


    /**
     * Reads a file and returns the contents as a <code>String</code>.
     *
     * @param   filename    name of the file (could include path)
     * @return  contents of file
     * @throws  IOException if a file exception occured
     */
    public static final String loadFile(final String filename)
            throws IOException {

        final StringBuffer buffer = new StringBuffer();
        loadFile(filename, buffer);
        return buffer.toString();
    }


    /**
     * Reads contents of a file into a string buffer.
     *
     * @param   filename    name of the file (could include path)
     * @param   buffer      buffer to fill with file contents
     * @throws  IOException if a file exception occured
     */
    public static final void loadFile(final String filename,
            final StringBuffer buffer)
            throws IOException {
        loadFile(new File(filename), buffer);
    }


    /**
     * Reads contents of a stream into a string buffer.
     *
     * @param   in          this stream will be loaded
     * @param   buffer      buffer to fill with file contents
     * @throws  IOException if a file exception occured
     */
    public static final void loadStream(final InputStream in,
            final StringBuffer buffer)
            throws IOException {

        buffer.setLength(0);
        int c;
        while ((c = in.read()) >= 0) {
            buffer.append((char) c);
        }
    }



    /**
     * Reads contents of a file into a string buffer.
     *
     * @param   file        this file will be loaded.
     * @param   buffer      buffer to fill with file contents.
     * @throws  IOException a file exception occured.
     */
    public static final void loadFile(final File file,
            final StringBuffer buffer)
            throws IOException {

        final int size = (int) file.length();
        buffer.setLength(0);
        final FileReader in = new FileReader(file);
        final char[] data = new char[size];
        int charsread = 0;
        while (charsread < size) {
            charsread += in.read(data, charsread, size-charsread);
        }
        in.close();
        buffer.insert(0, data);
    }


    /**
     * Saves a <code>String</code> in a file.
     *
     * @param   filename    name of the file (could include path).
     * @param   text        data to save in the file.
     * @throws  IOException a file exception occured.
     */
    public static final void saveFile(String filename, String text)
            throws IOException {
        saveFile(new File(filename), text);
    }


    /**
     * Saves a <code>StringBuffer</code> in a file.
     *
     * @param   filename    name of the file (could include path).
     * @param   text        data to save in the file.
     * @throws  IOException if a file exception occured.
     */
    public static final void saveFile(String filename, StringBuffer text)
            throws IOException {
        saveFile(new File(filename), text.toString());
    }


    /**
     * Saves a <code>StringBuffer</code> in a file.
     *
     * @param   filename    name of the file (could include path).
     * @param   text        data to save in the file.
     * @throws  IOException if a file exception occured.
     */
    public static final void saveFile(final File file, final StringBuffer text)
            throws IOException {
        saveFile(file, text.toString());
    }


    /**
     * Saves a <code>String</code> in a file. 
     *
     * @param   file        file.
     * @param   text        data to save in the file.
     * @throws  IOException a file exception occured.
     */
    public static final void saveFile(final File file, final String text)
            throws IOException {
        BufferedWriter out = new BufferedWriter(
            new FileWriter(file));
        out.write(text);
        out.close();
    }


    /**
     * Quotes a <code>String</code>. If no quotes exist in the
     * <code>String</code>, a quote character is appended at the
     * beginning and the end of the <code>String</code>.
     *
     * @param   unquoted    the ungequoted" <code>String</code>
     * @return  quoted <code>String</code>
     * @throws  NullPointerException if <code>unquoted == null</code>
     */
    public static final String quote(String unquoted) {

        String result = new String("\"");

        for (int i = 0; i < unquoted.length(); i++) {
            if (unquoted.charAt(i) == '\"') {
                result += "\"\"";
            } else {
                result += unquoted.charAt(i);
            }
        }
        result += '\"';
        return result;
    }



    /**
     * Tests if given <code>String</code> begins with a letter and contains
     * only letters and digits.
     *
     * @param   text    test this
     * @return  is <code>text</code> only made of letters and digits and has
     *          a leading letter?
     * @throws  NullPointerException if <code>text == null</code>
     */
    public static final boolean isLetterDigitString(final String text) {
        if (text.length() <= 0) {
            return false;
        }
        if (!Character.isLetter(text.charAt(0))) {
            return false;
        }
        for (int i = 1; i < text.length(); i++) {
            if (!Character.isLetterOrDigit(text.charAt(i))) {
                return false;
            }
        }
        return true;
    }


    /**
     * Replaces all occurences of <code>search</code> in <code>text</code>
     * by <code>replace</code> and returns the result.
     *
     * @param   text    text to work on
     * @param   search  replace this text by <code>replace</code>
     * @param   replace replacement for <code>search</code>
     * @return  resulting string
     */
    public final static String replace(final String text,
            final String search, final String replace) {

        final StringBuffer result = new StringBuffer();
        int pos1 = 0;
        int pos2;
        final int len = search.length();
        while (0 <= (pos2 = text.indexOf(search, pos1))) {
            result.append(text.substring(pos1, pos2));
            result.append(replace);
            pos1 = pos2 + len;
        }
        if (pos1 < text.length()) {
            result.append(text.substring(pos1));
        }
        return result.toString();
    }


    /**
     * Replaces all occurences of <code>search</code> in <code>text</code>
     * by <code>replace</code> and returns the result.
     *
     * @param   text    text to work on
     * @param   search  replace this text by <code>replace</code>
     * @param   replace replacement for <code>search</code>
     * @return  resulting string
     */
    public final static void replace(final StringBuffer text,
            final String search, final String replace) {

        final String result = replace(text.toString(), search, replace);
        text.setLength(0);
        text.append(result);
// TODO check if the above could be replaced with: 
/*
        final StringBuffer result = new StringBuffer();
        int pos1 = 0;
        int pos2;
        final int len = search.length();
        while (0 <= (pos2 = text.indexOf(search, pos1))) {
            result.append(text.substring(pos1, pos2));
            result.append(replace);
            pos1 = pos2 + len;
        }
        if (pos1 < text.length()) {
            result.append(text.substring(pos1));
        }
        text.setLength(0);
        text.append(result);
  */
      }


    /**
     * Waits til a '\n' was read from System.in.
     */
    public final static void waitln() {
        System.out.println("\n..press <return> to continue");
        try {
            (new java.io.BufferedReader(new java.io.InputStreamReader(
                System.in))).readLine();
        } catch (IOException e) {
        }
    }


    public static boolean deleteDir(File dir) {
        // to see if this directory is actually a symbolic link to a directory,
        // we want to get its canonical path - that is, we follow the link to
        // the file it's actually linked to
        File candir;
        try {
            candir = dir.getCanonicalFile();
        } catch (IOException e) {
            return false;
        }
  
        // a symbolic link has a different canonical path than its actual path,
        // unless it's a link to itself
        if (!candir.equals(dir.getAbsoluteFile())) {
            // this file is a symbolic link, and there's no reason for us to
            // follow it, because then we might be deleting something outside of
            // the directory we were told to delete
            return false;
        }
  
        // now we go through all of the files and subdirectories in the
        // directory and delete them one by one
        File[] files = candir.listFiles();
        if (files != null) {
            for (int i = 0; i < files.length; i++) {
                File file = files[i];
  
                // in case this directory is actually a symbolic link, or it's
                // empty, we want to try to delete the link before we try
                // anything
                boolean deleted = file.delete();
                if (!deleted) {
                    // deleting the file failed, so maybe it's a non-empty
                    // directory
                    if (file.isDirectory()) deleteDir(file);
  
                    // otherwise, there's nothing else we can do
                }
            }
        }
  
        // now that we tried to clear the directory out, we can try to delete it
        // again
        return dir.delete();  
    }


    /**
     * Get amount of spaces.
     *
     * @param   length  number of spaces
     * @return  String containig exactly <code>number</code> spaces
     */
    public final static StringBuffer getSpaces(int length) {
        final StringBuffer buffer = new StringBuffer(length);
        for (int i = 0; i < length; i++) {
            buffer.append(' ');
        }
        return buffer;
    }

    /**
     * Get non qualified class name.
     *
     * @param   clazz
     * @return  non qualified class name
     */
    public final static String getClassName(final Class clazz) {
        return clazz.getName().substring(clazz.getName().lastIndexOf('.') + 1);
    }


    public final static void printAllSystemProperties() {
        Properties sysprops = System.getProperties();
        for ( Enumeration e = sysprops.propertyNames( ); e.hasMoreElements(); ) {
            String key = (String) e.nextElement();
            String value = sysprops.getProperty(key );
            System.out.println (key + "=" + value);
        }         
    }


    public final static File getUserHomeDirectory() {
        return new File((String) System.getProperties().get("user.home"));
    }


    /**
     * Create relative address from <code>orgin</code> to <code>next</code>.
     *
     * @param   orgin   this is the original location
     * @param   next    this should be the next location
     * @return  relative (or if neccessary absolute) file path
     */
    public final static String createRelativePath(final File orgin,
            final File next) {
        final String METHOD = "createRelativePath(File, File)";
        try {
            Trace.traceBegin(Utility.class, METHOD);
            Trace.traceParam(Utility.class, METHOD, "orgin", orgin);
            Trace.traceParam(Utility.class, METHOD, "orgin.canonical", orgin.getCanonicalPath());
            Trace.traceParam(Utility.class, METHOD, "next", next);
            Trace.traceParam(Utility.class, METHOD, "next.canonical", next.getCanonicalPath());
            if (orgin.equals(next)) {
                return "";
            }
            try {
                String org = orgin.getCanonicalPath().replace('\\', '/');
                if (orgin.isDirectory() && !org.endsWith("/")) {
                    org += "/";
                }
                String nex = next.getCanonicalPath().replace('\\', '/');
                if (next.isDirectory() && !nex.endsWith("/")) {
                    nex += "/";
                }
                int i = -1; // position of next '/'
                int j = 0;  // position of last '/'
                while (0 <= (i = org.indexOf("/", j))) {
                    if (i >= 0 && nex.length() > i
                            && org.substring(j, i).equals(
                            nex.substring(j, i))) {
                        j = i + 1;
                    } else {
                        break;
                    }
                }

                if (j > 0) {
                    i = j;
                    StringBuffer result = new StringBuffer(nex.length());
                    while (0 <= (i = org.indexOf("/", i))) {
                        i++;
                        result.append("../");
                    }
                    result.append(nex.substring(j));
                    Trace.traceParam(Utility.class, METHOD, "result", result);
                    return result.toString();
                } else {
                    return "/" + nex;
                }
    //            } else {    // no relative address posible
    //                return urlNext.toString();
    //            }
            } catch (RuntimeException e) {
                Trace.trace(Utility.class, METHOD, e);
                return next.toString();
            }
        } catch (IOException e) {
            Trace.trace(Utility.class, METHOD, e);
            return next.toString();
        } finally {
            Trace.traceEnd(Utility.class, METHOD);
        }

    }





}
