package wox.serial;

import org.jdom.Element;
import org.jdom.Comment;

import java.lang.reflect.*;
import java.util.*;


// import crjaim.EncodeBase64;

/**
 *  A simple but useful Object to XML serialiser.
 *  By Simon M. Lucas, August 2004
 *  Base 64 modifications by Carlos R. Jaimez Gonzalez
 */

public class SimpleWriter implements ObjectWriter {

    HashMap map;

    int count;
    boolean writePrimitiveTypes = true;
    boolean doStatic = true;

    // not much point writing out final values - at least yet -
    // the reader is not able to set them (though there's probably
    // a hidden way of doing this
    boolean doFinal = false;


    public SimpleWriter() {
        //System.out.println("inside SimpleWriter Constructor...");
        map = new HashMap();
        count = 0;

    }

    public Element write(Object ob) {
        Element el;
        if (ob == null) {
            // a null object is represented by an empty Object tag with no attributes
            return new Element(OBJECT);
        }
        if (map.get(ob) != null) {
            el = new Element(OBJECT);
            el.setAttribute(IDREF, map.get(ob).toString());
            return el;
        }
        // a previously unseen object...
        map.put(ob, new Integer(count++));
        if (Util.stringable(ob)) {
            el = new Element(OBJECT);
            el.setAttribute(TYPE, ob.getClass().getName());
            el.setText(stringify(ob));
        } else if (ob.getClass().isArray()) {
            el = writeArray(ob);
        } else {
            el = new Element(OBJECT);
            el.setAttribute(TYPE, ob.getClass().getName());
            writeFields(ob, el);
        }
        el.setAttribute(ID, map.get(ob).toString());
        return el;
    }

    public Element writeArray(Object ob) {
        if (isPrimitiveArray(ob.getClass())) {
            return writePrimitiveArray(ob);
        } else {
            return writeObjectArray(ob);
        }
    }

    public Element writeObjectArray(Object ob) {
        Element el = new Element(ARRAY);
        // el.setAttribute
        // int[].class.
        // Array.
        el.setAttribute(TYPE, ob.getClass().getComponentType().getName());
        int len = Array.getLength(ob);
        el.setAttribute(LENGTH, "" + len);
        for (int i = 0; i < len; i++) {
            el.addContent(write(Array.get(ob, i)));
        }
        return el;
    }

    public Element writePrimitiveArray(Object ob) {
        Element el = new Element(ARRAY);
        el.setAttribute(TYPE, ob.getClass().getComponentType().getName());
        int len = Array.getLength(ob);
        //CJ this should not be here beacsue the lenght for the byte[] can be different
        //el.setAttribute(LENGTH, "" + len);
        if (ob instanceof byte[]) {
            el.setText(byteArrayString((byte[]) ob, el));
        } else {
            el.setAttribute(LENGTH, "" + len);
            el.setText(arrayString(ob, len));
        }
        return el;
    }

    //method modified to include base64 encoding
    public String byteArrayString(byte[] a, Element e) {
        byte[] target = EncodeBase64.encode(a);
        //set the lenght fro the new encoded array
        e.setAttribute(LENGTH, "" + target.length);
        String strTarget = new String(target);
        return strTarget;
    }

    public String arrayString(Object ob, int len) {
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < len; i++) {
            if (i > 0) {
                sb.append(" ");
            }
            sb.append(Array.get(ob, i).toString());
        }
        return sb.toString();
    }


    public void writeFields(Object o, Element parent) {
        // get the class of the object
        // get its fields
        // then get the value of each one
        // and call write to put the value in the Element

        Class cl = o.getClass();
        Field[] fields = getFields(cl);
        String name = null;
        for (int i = 0; i < fields.length; i++) {
            if ((doStatic || !Modifier.isStatic(fields[i].getModifiers())) &&
                    (doFinal || !Modifier.isFinal(fields[i].getModifiers())))
                try {
                    fields[i].setAccessible(true);
                    name = fields[i].getName();
                    // need to handle shadowed fields in some way...
                    // one way is to add info about the declaring class
                    // but this will bloat the XML file if we di it for
                    // every field - might be better to just do it for
                    // the shadowed fields
                    // name += "." + fields[i].getDeclaringClass().getName();
                    // fields[i].
                    Object value = fields[i].get(o);
                    Element field = new Element(FIELD);
                    field.setAttribute(NAME, name);
                    if (shadowed(fields, name)) {
                        field.setAttribute(DECLARED, fields[i].getDeclaringClass().getName());
                    }
                    if (fields[i].getType().isPrimitive()) {
                        // this is not always necessary - so it's optional
                        if (writePrimitiveTypes) {
                            field.setAttribute(TYPE, fields[i].getType().getName());
                        }
                        field.setAttribute(VALUE, value.toString());

                    } else {
                        field.addContent(write(value));
                    }
                    parent.addContent(field);
                } catch (Exception e) {
                    e.printStackTrace();
                    System.out.println(e);
                    // at least comment on what went wrong
                    parent.addContent(new Comment(e.toString()));
                }
        }
    }

    private boolean shadowed(Field[] fields, String fieldName) {
        // count the number of fields with the name fieldName
        // return true if greater than 1
        int count = 0;
        for (int i = 0; i < fields.length; i++) {
            if (fieldName.equals(fields[i].getName())) {
                count++;
            }
        }
        return count > 1;
    }

    public static String stringify(Object ob) {
        if (ob instanceof Class) {
            return ((Class) ob).getName();
        } else {
            return ob.toString();
        }
    }

    public static Field[] getFields(Class c) {
        Vector v = new Vector();
        while (!(c == null)) { // c.equals( Object.class ) ) {
            Field[] fields = c.getDeclaredFields();
            for (int i = 0; i < fields.length; i++) {
                // System.out.println(fields[i]);
                v.addElement(fields[i]);
            }
            c = c.getSuperclass();
        }
        Field[] f = new Field[v.size()];
        for (int i = 0; i < f.length; i++) {
            f[i] = (Field) v.get(i);
        }
        return f;
    }

    public static Object[] getValues(Object o, Field[] fields) {
        Object[] values = new Object[fields.length];
        for (int i = 0; i < fields.length; i++) {
            try {
                fields[i].setAccessible(true);
                values[i] = fields[i].get(o);
                System.out.println(fields[i].getName() + "\t " + values[i]);
            } catch (Exception e) {
                System.out.println(e);
            }
        }
        return values;
    }

    public boolean isPrimitiveArray(Class c) {
        for (int i = 0; i < primitiveArrays.length; i++) {
            if (c.equals(primitiveArrays[i])) {
                return true;
            }
        }
        return false;
    }


}