/*
 * Steganography utility to hide messages into cover files
 * Author: Samir Vaidya (mailto:[email protected])
 * Copyright (c) Samir Vaidya
 */

package com.openstego.desktop.util;

import java.awt.Color;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;

import javax.swing.JTextField;
import javax.swing.UIManager;

import com.openstego.desktop.OpenStegoException;

/**
 * Common utilities for OpenStego
 */
public class CommonUtil {
    /**
     * Constructor is private so that this class is not instantiated
     */
    private CommonUtil() {
    }

    /**
     * Method to get byte array data from given InputStream
     *
     * @param is InputStream to read
     * @return Stream data as byte array
     * @throws OpenStegoException
     */
    public static byte[] getStreamBytes(InputStream is) throws OpenStegoException {
        final int BUF_SIZE = 512;
        ByteArrayOutputStream bos = null;
        int bytesRead = 0;
        byte[] data = null;

        try {
            data = new byte[BUF_SIZE];
            bos = new ByteArrayOutputStream();

            while ((bytesRead = is.read(data, 0, BUF_SIZE)) >= 0) {
                bos.write(data, 0, bytesRead);
            }

            is.close();
            bos.close();

            return bos.toByteArray();
        } catch (IOException ioEx) {
            throw new OpenStegoException(ioEx);
        }
    }

    /**
     * Method to get byte array data from given file
     *
     * @param file File to read
     * @return File data as byte array
     * @throws OpenStegoException
     */
    public static byte[] getFileBytes(File file) throws OpenStegoException {
        try {
            return getStreamBytes(new FileInputStream(file));
        } catch (IOException ioEx) {
            throw new OpenStegoException(ioEx);
        }
    }

    /**
     * Method to write file data to disk
     *
     * @param fileData File data
     * @param fileName File name (If this is <code>null</code>, then data is written to stdout)
     * @throws OpenStegoException
     */
    public static void writeFile(byte[] fileData, String fileName) throws OpenStegoException {
        File file = null;

        if (fileName != null) {
            file = new File(fileName);
        }
        writeFile(fileData, file);
    }

    /**
     * Method to write file data to disk
     *
     * @param fileData File data
     * @param file File object (If this is <code>null</code>, then data is written to stdout)
     * @throws OpenStegoException
     */
    public static void writeFile(byte[] fileData, File file) throws OpenStegoException {
        OutputStream os = null;

        try {
            // If file is not provided, then write the data to stdout
            if (file == null) {
                os = System.out;
            } else {
                os = new FileOutputStream(file);
            }
            os.write(fileData);
            os.close();
        } catch (IOException ioEx) {
            throw new OpenStegoException(ioEx);
        }
    }

    /**
     * Method to enable/disable a Swing JTextField object
     *
     * @param textField Swing JTextField object
     * @param enabled Flag to indicate whether to enable or disable the object
     */
    public static void setEnabled(JTextField textField, boolean enabled) {
        if (enabled) {
            textField.setEnabled(true);
            textField.setBackground(Color.WHITE);
        } else {
            textField.setEnabled(false);
            textField.setBackground(UIManager.getColor("Panel.background"));
        }
    }

    /**
     * Method to parse a delimiter separated list of files into arraylist of filenames. It supports wildcard characters
     * "*" and "?" within the filenames.
     *
     * @param fileList Delimiter separated list of filenames
     * @param delimiter Delimiter for tokenization
     * @return List of filenames after tokenizing and wildcard expansion
     */
    public static List<File> parseFileList(String fileList, String delimiter) {
        int index = 0;
        StringTokenizer tokenizer = null;
        String fileName = null;
        String dirName = null;
        List<File> output = new ArrayList<File>();
        File fileDir = null;
        File[] arrFile = null;

        if (fileList == null) {
            return output;
        }

        tokenizer = new StringTokenizer(fileList, delimiter);
        while (tokenizer.hasMoreTokens()) {
            fileName = tokenizer.nextToken().trim();
            index = fileName.lastIndexOf(File.separator);

            if (index >= 0) {
                dirName = fileName.substring(0, index);
                fileName = fileName.substring(index + 1);
            } else {
                dirName = ".";
            }
            fileName = replaceWildcards(fileName);

            fileDir = new File(dirName.equals("") ? "." : dirName);
            arrFile = fileDir.listFiles(new WildcardFilenameFilter(fileName));

            for (int i = 0; i < arrFile.length; i++) {
                output.add(arrFile[i]);
            }
        }

        return output;
    }

    /**
     * Byte to Int converter
     *
     * @param b Input byte value
     * @return Int value
     */
    public static int byteToInt(int b) {
        int i = b;
        if (i < 0) {
            i = i + 256;
        }
        return i;
    }

    /**
     * Helper method to replace file wildcard characters with Java regexp wildcard chararcters
     *
     * @param input Input String
     * @return String containing modified wildcard characters
     */
    private static String replaceWildcards(String input) {
        StringBuffer buffer = new StringBuffer();
        char[] chars = input.toCharArray();

        for (int i = 0; i < chars.length; i++) {
            if (chars[i] == '*') {
                buffer.append(".*");
            } else if (chars[i] == '?') {
                buffer.append(".{1}");
            } else if ("+()^$.{}[]|\\".indexOf(chars[i]) != -1) // Escape rest of the java regexp wildcards
            {
                buffer.append('\\').append(chars[i]);
            } else {
                buffer.append(chars[i]);
            }
        }

        return buffer.toString();
    }

    /**
     * Inner class for wildcard filename filter
     */
    static class WildcardFilenameFilter implements FilenameFilter {
        /**
         * Variable to hold the filter string
         */
        String filter = null;

        /**
         * Default constructor
         *
         * @param filter Filter string
         */
        public WildcardFilenameFilter(String filter) {
            this.filter = filter.toLowerCase();
        }

        /**
         * Implementation of <code>accept</code> method
         *
         * @param dir Directory to traverse
         * @param name Name of the file
         * @return Whether file is accepted by the filter or not
         */
        @Override
        public boolean accept(File dir, String name) {
            return (name.toLowerCase().matches(this.filter));
        }
    }

    /**
     * Returns the floor of the half of the input value
     *
     * @param num Input number
     * @return Floor of the half of the input number
     */
    public static int floorHalf(int num) {
        if ((num & 1) == 1) {
            return (num - 1) / 2;
        } else {
            return num / 2;
        }
    }

    /**
     * Returns the ceiling of the half of the input value
     *
     * @param num Input number
     * @return Ceiling of the half of the input number
     */
    public static int ceilingHalf(int num) {
        if ((num & 1) == 1) {
            return (num + 1) / 2;
        } else {
            return num / 2;
        }
    }

    /**
     * Returns the modulus of the input value (taking care of the sign of the value)
     *
     * @param num Input number
     * @param div Divisor for modulus
     * @return Modulus of num by div
     */
    public static int mod(int num, int div) {
        if (num < 0) {
            return div - (-num % div);
        } else {
            return num % div;
        }
    }

    /**
     * Get maximum of two given values
     *
     * @param x Value 1
     * @param y value 2
     * @return Max of the two values
     */
    public static int max(int x, int y) {
        return (x > y) ? x : y;
    }

    /**
     * Get maximum of two given values
     *
     * @param x Value 1
     * @param y value 2
     * @return Max of the two values
     */
    public static double max(double x, double y) {
        return (x > y) ? x : y;
    }

    /**
     * Get minimum of two given values
     *
     * @param x Value 1
     * @param y value 2
     * @return Min of the two values
     */
    public static int min(int x, int y) {
        return (x < y) ? x : y;
    }

    /**
     * Get minimum of two given values
     *
     * @param x Value 1
     * @param y value 2
     * @return Min of the two values
     */
    public static double min(double x, double y) {
        return (x < y) ? x : y;
    }
}