package vermilion;

import java.util.ArrayList;

/**
 * A base class for things we want to store in the composition. The subclass
 * may store any information it likes provided it can serialise and deserialise
 * that data to a string on demand. It can then be used either as runtime data
 * or a one-time copy of said data created simply for storage or retrieval.
 *
 * To update the data in a file, the UsefulFileObject must be passed (back) to
 * the UsefulFile in a setValue operation. See UsefulFile for more information.
 *
 * It all makes much more sense if you simply take a look at UsefulFileObjectSimple.
 */
public abstract class UsefulFileObject
{
    /**
     * Returns a string identifying the type of object being stored. This can
     * be anything but must be unique and should not contain ';' or ':'.
     * @return A type that uniquely identifies a subclass for storage.
     */
    public abstract String getType();

    /**
     * Returns an integer identifying which version of the object is being
     * used. Basically if the method of storage changes this number should
     * be incremented, and new decoding code written in loadImpl. If the
     * actual structure being stored has changed, patching code can be written
     * into the old loading routines in order to facilitate upgrading the file
     * as it is loaded.
     * @return A version number that is incremented any time you change the
     * implementation of save().
     */
    public abstract int getVersion();

    /**
     * The load method reads a string from a document node and returns the
     * UsefulFileObject subclass that wrote the data at that node. The base
     * method takes care of reading the type and version of the data that has
     * been written, and passes it to a subclass that can read the data.
     * @param str A serialised UsefulFileObject.
     * @return The deserialised UsefulFileOBject.
     */
    public static UsefulFileObject load(String str)
    {
        int prefixIndex = str.indexOf(":");
        if (prefixIndex > 0)
        {
            String prefix = str.substring(0, prefixIndex);
            int versIndex = str.indexOf(";");

            String typeStr = prefix.substring(0, versIndex);
            String versStr = prefix.substring(versIndex+1);
            int version = Integer.parseInt(versStr);

            UsefulFileObject type = UsefulFile.getRegisteredType(typeStr);
            if (type != null)
            {
                try
                {
                    return type.loadImpl(str.substring(prefixIndex+1), version);
                }
                catch (Exception e) { System.out.println("Error loading type " + typeStr + " - " + e.toString()); }
            }

            System.out.println("Unknown type " + typeStr);
        }
        return null;
    }

    /**
     * The base save method simply prefixes the serialised data from the subclass
     * with the type and version of the subclass.
     * @return A serialised version of the object.
     */
    public String save()
    {
        return getType() + ";" + getVersion() + ":" + saveImpl();
    }

    /**
     * Returns the serialised data for the UsefulFileObject.
     * @return A string representing the data on your UsefulFileObject.
     */
    public abstract String saveImpl();

    /**
     * Returns a deserialised object from serialised data.
     * @param str A string of serialised object data.
     * @param version The version number in use when the string was written.
     * @return A UsefulFileObject loaded with the deserialised data.
     */
    public abstract UsefulFileObject loadImpl(String str, int version);

    /**
     * A utility method to turn a list of strings into a single string. This
     * takes care of formatting so the method can be used recursively.
     * @param list An ArrayList of strings.
     * @return A string that can be reinterpreted as the list later on.
     */
    public static String listToString(ArrayList<String> list)
    {
        String str = "";

        for (int x = 0; x < list.size(); x++)
        {
            String sub = list.get(x);
            String mod = sub.replace("\\", "\\\\");
            mod = mod.replace("\n", "\\n");
            str += mod;
            if (x < list.size()-1) str += "\n";
        }

        return str;
    }

    /**
     * Returns a list of strings given a previously encoded list as a string.
     * @param str The string of encoded strings.
     * @return A list of strings, or an empty list if nothing is found.
     */
    public static ArrayList<String> stringToList(String str)
    {
        ArrayList<String> list = new ArrayList<String>();

        String[] subs = str.split("\n");

        for (String sub : subs)
        {
            //System.out.println("splitting: " + sub);

            // Sort out substring \n vs \\n problem...
            String[] hack = sub.split("\\\\\\\\"); // code->regex->string = "\\"
            String result = "";

            for (int x = 0; x < hack.length; x++)
            {
                //System.out.println("split: " + hack[x]);

                String hackSub = hack[x].replace("\\n", "\n");

                result += hackSub;
                if (x < hack.length-1) result += "\\";
            }

            if (result.length() > 0) list.add(result);
        }

        return list;
    }
}
