/*
 * MegaMek - Copyright (C) 2000-2002 Ben Mazur (bmazur@sev.org)
 * 
 * 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 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.
 */

/*
 * BuildingBlock.java
 * 
 * Created on April 2, 2002, 1:57 PM
 */

/**
 * 
 * @author Nate Rowden
 * @version 1
 */

package megamek.common.util; // add to this package so BLKMechFile can read

// it's files...

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Vector;

/**
 * buildingBlock is based on a file format I used in an online game. The original was written in PHP, this one is more robust, and written in Java.
 */
public class BuildingBlock {

    private Vector<String> rawData;
    private static final int version = 1;
    private static final char comment = '#';

    /**
     * Creates new empty buildingBlock
     */
    public BuildingBlock() {
        // for holding the file we read/parse
        rawData = new Vector<String>();
    }

    /**
     * Creates a new buildingBlock and fills it with the data in the String[] array.
     * 
     * @param data
     *            This is most usefull for storing one block file inside another...but <I>data</I> can be an array of anything...such as comments.
     */
    public BuildingBlock(String[] data) {
        rawData = new Vector<String>();

        rawData = makeVector(data);
    }

    /**
     * Creates a new buildingBlock and fills it with the Vector.
     * 
     * @param data
     *            The Vector can be filled with anything.
     */
    public BuildingBlock(Vector<String> data) {
        rawData = data;
    }

    public BuildingBlock(InputStream is) {
        rawData = new Vector<String>();

        readInputStream(is);
    }

    public boolean readInputStream(InputStream is) {
        String data;
        BufferedReader in;

        in = new BufferedReader(new InputStreamReader(is));

        // empty the rawData holder...
        rawData.clear();

        try {

            // read the file till can't read no more...
            while (in.ready()) {

                data = in.readLine();
                if (data == null) {
                    continue;
                }
                data = data.trim();

                // check for blank lines & comment lines...
                // don't add them to the rawData if they are
                if (data.length() > 0 && !data.startsWith("" + BuildingBlock.comment)) {
                    rawData.add(data);
                }
            }
            in.close();
        } catch (IOException e) {
            System.err.println("An IO Exception occured while attempting to read a BuildingBlock stream."); //$NON-NLS-1$
            return false;
        }
        return true;
    }

    /**
     * Finds the starting index of a block. This is used by the class to locate data, but is a public function that can be useful if you know what you want to
     * do with the <CODE>rawData</CODE> Vector.
     * 
     * @param blockName
     *            The name of the data block to locate.
     * @return Returns the start index of the block data. Or -1 if not found.
     * @see findEndIndex()
     * @see getAllDataAsVector()
     */
    public int findStartIndex(String blockName) {

        String line;
        int startIndex = -1;
        StringBuffer buf = new StringBuffer();
        String key = null;

        // Translate the block name to a key.
        buf.append('<').append(blockName).append('>');
        key = buf.toString();
        buf = null;

        // look for the block...
        for (int lineNum = 0; lineNum < rawData.size(); lineNum++) {

            line = rawData.get(lineNum).toString();

            // look for "<blockName>"
            try {
                if (line.length() >= 3 && line.equalsIgnoreCase(key)) {
                    startIndex = ++lineNum;
                    break;
                }
            } catch (StringIndexOutOfBoundsException e) {

                System.err.print("Was looking for "); //$NON-NLS-1$
                System.err.print(key);
                System.err.println(" and caught a"); //$NON-NLS-1$
                System.err.print("string index out of bounds exception on line: \""); //$NON-NLS-1$
                System.err.print(line);
                System.err.println("\""); //$NON-NLS-1$
                System.err.print("rawData index number: "); //$NON-NLS-1$
                System.err.println(lineNum);

            }
        }
        return startIndex;
    }

    /**
     * Finds the starting index of a block. This is used by the class to locate data, but is a public function that can be useful if you know what you want to
     * do with the <CODE>rawData</CODE> Vector.
     * 
     * @param blockName
     *            The name of the data block to locate.
     * @return Returns the end index of the block data. Or -1 if not found.
     * @see findStartIndex()
     * @see getAllDataAsVector()
     */
    public int findEndIndex(String blockName) {
        String line;
        int endIndex = -1;
        StringBuffer buf = new StringBuffer();
        String key = null;

        // Translate the block name to a key.
        buf.append('<').append('/').append(blockName).append('>');
        key = buf.toString();
        buf = null;

        // look for the block...
        for (int lineNum = 0; lineNum < rawData.size(); lineNum++) {

            line = rawData.get(lineNum).toString();

            // look for "</blockName>"
            try {
                if (line.length() >= 3 && line.equalsIgnoreCase(key)) {
                    endIndex = lineNum;
                    break;
                }
            } catch (StringIndexOutOfBoundsException e) {

                System.err.print("Was looking for "); //$NON-NLS-1$
                System.err.print(key);
                System.err.println(" and caught a"); //$NON-NLS-1$
                System.err.print("string index out of bounds exception on line: \""); //$NON-NLS-1$
                System.err.print(line);
                System.err.println("\""); //$NON-NLS-1$
                System.err.print("rawData index number: "); //$NON-NLS-1$
                System.err.println(lineNum);
            }
        }
        return endIndex;
    }

    /**
     * Gets data from inside a block.
     * 
     * @param blockName
     *            The name of the block to grab the data from.
     * @return Returns an array of data.
     */
    public String[] getDataAsString(String blockName) {

        String[] data;
        int startIndex = 0, endIndex = 0;

        startIndex = findStartIndex(blockName);

        endIndex = findEndIndex(blockName);

        if (startIndex == -1 || endIndex == -1) {

            data = new String[1];
            data[0] = "0"; //$NON-NLS-1$
            return data;
        }

        // calculate the size of our data array by subtracting the two indexes
        // ...
        int size = endIndex - startIndex;

        if (size == 0) {
            data = new String[size + 1]; // add one so we always have at
            // least a size 1 array...
        } else {
            data = new String[size];
        }

        int dataRecord = 0;

        // fill up the data array with the raw data we want...
        for (int rawRecord = startIndex; rawRecord < endIndex; rawRecord++) {
            data[dataRecord] = rawData.get(rawRecord).toString();
            dataRecord++;
        }
        return data; // hand back the goods...
    }

    /**
     * @see getDataAsString()
     */
    public int[] getDataAsInt(String blockName) {

        int[] data;
        int startIndex, endIndex;

        startIndex = findStartIndex(blockName);

        endIndex = findEndIndex(blockName);

        if (startIndex == -1 || endIndex == -1) {

            data = new int[1];
            data[0] = 0;
            return data;

        }

        // calculate the size of our data array by subtracting the two indexes
        // ...

        int size = endIndex - startIndex;

        if (size == 0) {
            data = new int[size + 1]; // add one so we always have at least a
            // size 1 array...
        } else {
            data = new int[size];
        }

        int dataRecord = 0;

        // fill up the data array with the raw data we want...
        for (int rawRecord = startIndex; rawRecord < endIndex; rawRecord++) {

            try {
                // Bug with people placing , in the fuel strings like 18,000
                // Should probably change this to a method to weed out all non-numeric
                // variables but this is the most common.
                String rawString = rawData.get(rawRecord).toString();
                if (rawString.indexOf(',') >= 0) {
                    rawString = rawString.replaceAll(",", "");
                }
                data[dataRecord] = Integer.parseInt(rawString);
                dataRecord++;
            } catch (NumberFormatException oops) {
                data[0] = 0;
                System.err.println("getDataAsInt(\"" + blockName + "\") failed.  NumberFormatException was caught."); //$NON-NLS-1$ //$NON-NLS-2$
                oops.printStackTrace();
            }
        }
        return data; // hand back the goods...
    }

    /**
     * @see getDataAsString()
     */
    public float[] getDataAsFloat(String blockName) {

        float[] data;
        int startIndex, endIndex;

        startIndex = findStartIndex(blockName);

        endIndex = findEndIndex(blockName);

        if (startIndex == -1 || endIndex == -1) {

            data = new float[1];
            data[0] = 0;
            return data;

        }

        // calculate the size of our data array by subtracting the two indexes
        // ...

        int size = endIndex - startIndex;

        if (size == 0) {
            data = new float[size + 1]; // add one so we always have at least a
            // size 1 array...
        } else {
            data = new float[size];
        }

        int dataRecord = 0;

        // fill up the data array with the raw data we want...
        for (int rawRecord = startIndex; rawRecord < endIndex; rawRecord++) {

            try {

                data[dataRecord] = Float.valueOf(rawData.get(rawRecord).toString()).floatValue();
                dataRecord++;

            } catch (NumberFormatException oops) {

                data[0] = 0;
                System.err.println("getDataAsFloat(\"" + blockName + "\") failed.  NumberFormatException was caught."); //$NON-NLS-1$ //$NON-NLS-2$

            }

        }

        return data; // hand back the goods...

    }

    /**
     * Gets data from a block.
     * 
     * @param blockName
     *            Name of the block to get data from.
     * @return Returns the data as a Vector.
     */
    public Vector<String> getDataAsVector(String blockName) {

        Vector<String> data;
        int startIndex = 0, endIndex = 0;

        startIndex = findStartIndex(blockName);

        endIndex = findEndIndex(blockName);

        if (startIndex == -1 || endIndex == -1) {

            data = new Vector<String>();
            data.clear();
            return data;

        }

        data = new Vector<String>();

        // fill up the data vector with the raw data we want...
        for (int rawRecord = startIndex; rawRecord < endIndex; rawRecord++) {

            data.add(rawData.get(rawRecord));

        }

        return data; // hand back the goods...

    }

    /**
     * Clears the <CODE>rawData</CODE> Vector and inserts a default comment and <I>BlockVersion</I> information.
     * 
     * @return Returns true on success.
     */
    public boolean createNewBlock() {

        rawData.clear();

        writeBlockComment("building block data file"); //$NON-NLS-1$
        rawData.add(new String("")); // blank line.. //$NON-NLS-1$

        this.writeBlockData("BlockVersion", "" + BuildingBlock.version); //$NON-NLS-1$ //$NON-NLS-2$

        return true;
    }

    // to make life easier...

    /**
     * @see writeBlockData (String, Vector)
     */
    public boolean writeBlockData(String blockName, String blockData) {

        String[] temp = new String[1];
        temp[0] = blockData;

        return writeBlockData(blockName, makeVector(temp));

    }

    /**
     * @see writeBlockData (String, Vector)
     */
    public boolean writeBlockData(String blockName, int blockData) {

        String[] temp = new String[1];
        temp[0] = "" + blockData; //$NON-NLS-1$
        return writeBlockData(blockName, makeVector(temp));

    }

    /**
     * @see writeBlockData (String, Vector)
     */
    public boolean writeBlockData(String blockName, int[] blockData) {

        String[] temp = new String[blockData.length];

        for (int c = 0; c < blockData.length; c++) {

            temp[c] = "" + blockData[c]; //$NON-NLS-1$

        }
        return writeBlockData(blockName, makeVector(temp));

    }

    /**
     * @see writeBlockData (String, Vector)
     */
    public boolean writeBlockData(String blockName, float blockData) {

        String[] temp = new String[1];
        temp[0] = "" + blockData; //$NON-NLS-1$
        return writeBlockData(blockName, makeVector(temp));

    }

    /**
     * @see writeBlockData (String, Vector)
     */
    public boolean writeBlockData(String blockName, float[] blockData) {

        String[] temp = new String[blockData.length];

        for (int c = 0; c < blockData.length; c++) {

            temp[c] = "" + blockData[c]; //$NON-NLS-1$

        }

        return writeBlockData(blockName, makeVector(temp));

    }

    /**
     * @see writeBlockData (String, Vector)
     */
    public boolean writeBlockData(String blockName, String[] blockData) {

        return writeBlockData(blockName, makeVector(blockData));

    }

    /**
     * Writes a data block to the <CODE>rawData</CODE> vector.
     * 
     * @param blockName
     *            Name of the block to be created.
     * @param blockData
     *            Data to be written inside the block.
     * @return Returns true on success.
     */
    public boolean writeBlockData(String blockName, Vector<String> blockData) {

        rawData.add(new String("<" + blockName + ">")); //$NON-NLS-1$ //$NON-NLS-2$

        for (int c = 0; c < blockData.size(); c++) {
            rawData.add(blockData.get(c).trim());
        }

        rawData.add(new String("</" + blockName + ">")); //$NON-NLS-1$ //$NON-NLS-2$
        rawData.add(new String("")); //$NON-NLS-1$

        return true;

    }

    /**
     * Writes a comment.
     * 
     * @param theComment
     *            The comment to be written.
     * @return Returns true on success.
     */
    public boolean writeBlockComment(String theComment) {

        rawData.add(BuildingBlock.comment + theComment);
        return true;

    }

    /**
     * Writes the buildingBlock data to a file.
     * 
     * @param fileName
     *            File to write. Overwrites existing files.
     * @return Returns true on success.
     */
    public boolean writeBlockFile(String fileName) {

        File file = new File(fileName);

        if (file.exists()) {
            if (!file.delete()) {

                System.err.println("Unable to delete file...(so I could re-write it)"); //$NON-NLS-1$
                return false;
            }
        }

        try {

            // file.createNewFile();

            BufferedWriter out = new BufferedWriter(new FileWriter(file));

            for (int c = 0; c < rawData.size(); c++) {

                out.write(rawData.get(c).toString());
                out.newLine();

            }

            out.flush();
            out.close();
        } catch (IOException e) {

            System.err.println("Unable to save block file " + fileName); //$NON-NLS-1$
            return false;
        }

        return true;
    }

    /**
     * Clears the <CODE>rawData</CODE> Vector.
     */
    public void clearData() {

        rawData.clear();

    }

    /**
     * Gets the size of the <CODE>rawData</CODE> Vector.
     * 
     * @return Returns <CODE>rawData.size()</CODE>
     */
    public int dataSize() {

        return rawData.size();

    }

    /**
     * Converts a String array into a Vector.
     * 
     * @param stringArray
     *            The String array to convert.
     * @return Returns the Vector created by the String[]
     */
    public Vector<String> makeVector(String[] stringArray) {

        Vector<String> newVect = new Vector<String>();
        int c = 0;

        try {

            for (c = 0; c < stringArray.length; c++) {

                // this should throw an expection when we hit the end
                stringArray[c] = stringArray[c].trim();

                newVect.add(stringArray[c]);

            }

        } catch (ArrayIndexOutOfBoundsException e) {

            // we're done...return the vector
            return newVect;

        }

        return newVect; // just to make sure ; -?

    }

    /**
     * Useful if you want to copy one buildingBlock into another.
     * 
     * @return Returns the <CODE>rawData</CODE> Vector.
     */
    public Vector<String> getVector() {

        return rawData;

    }

    /**
     * Gets all the data inside the <CODE>rawData</CODE> Vector.
     * 
     * @return Returns the data as a String array
     */
    public String[] getAllDataAsString() {

        String[] theData = new String[rawData.size()];

        for (int c = 0; c < rawData.size(); c++) {

            theData[c] = rawData.get(c).toString();

        }

        return theData;

    }

    /**
     * Just about the same as the <CODE>getVector()</CODE> command.
     * 
     * @see getVector ()
     * @return Returns the <CODE>rawData</CODE> Vector.
     */
    public Vector<String> getAllDataAsVector() {

        Vector<String> theData = rawData; // can I jsut return this?

        return theData;

    }

    /**
     * Tells you the size of an array this thing returned by giving you the number in the [0] position.
     * 
     * @param array
     *            The array to get the size of.
     * @return Returns the number in the [0] position.
     */
    public int getReturnedArraySize(String[] array) {

        try {

            return Integer.parseInt(array[0]);

        } catch (NumberFormatException e) {

            // couldn't parse it...
            System.err.println("Couldn't find array size at [0]...is this an array I returned...?"); //$NON-NLS-1$
            System.err.println("Trying to find size anyway..."); //$NON-NLS-1$
            return this.countArray(array);
        }

    }

    // for those of us who like doing things indirectly ; -?
    /**
     * @see getReturnedArraySize (String[])
     */
    public int getReturnedArraySize(int[] array) {
        return array[0];
    }

    /**
     * @see getReturnedArraySize (String[])
     * @return Returns <CODE>array.size()</CODE>
     */
    public int getReturnedArraySize(Vector<Object> array) {
        return array.size();
    }

    /**
     * @see getReturnedArraySize (String[])
     */
    public int getReturnedArraySize(float[] array) {

        try {
            return Integer.parseInt("" + array[0]); //$NON-NLS-1$
        } catch (NumberFormatException e) {

            System.err.println("Couldn't find array size at [0]...is this an array I returned...?"); //$NON-NLS-1$
            System.err.println("Trying to find size anyway..."); //$NON-NLS-1$
            return this.countArray(array);
        }

    }

    /**
     * Counts the size of an array.
     * 
     * @param array
     *            The array to count.
     * @return Returns the array's size.
     */
    public int countArray(String[] array) {

        return array.length;

    }

    /**
     * @see countArray( String[] )
     */
    public int countArray(float[] array) {

        return array.length;
    }

    /**
     * @see countArray( String[] )
     */
    public int countArray(int[] array) {

        return array.length;
    }

    /**
     * Checks to see if a block exists...returns true or false
     */
    public boolean exists(String blockName) {

        if (findStartIndex(blockName) == -1) {
            return false;
        }
        if (findEndIndex(blockName) == -1) {
            return false;
        }

        return true;
    }
}
