package code.term.visitor;

import java.util.Vector;

import code.table.*;
import code.term.*;

/**
 * @author Stephen Johnson
 * @since Jul 6, 2004

 * Largely rewritten on Mon Apr 11 12:29:56 PDT 2005
 * by Sergio Antoy to correct how (1:X) is printed.
 */
public class ToStringTermVisitor implements TermVisitor<String,Integer> {

    /**
     * If true, print the identity of terms. 
     * Should be used for tough debugging only.
     */ 
    private static final boolean identity = false;

    public static final ToStringTermVisitor singleton
        = new ToStringTermVisitor();

    private ToStringTermVisitor() {}

    /* in pakcs

prelude> (map chr [1..255])
Result: "\001\002\003\004\005\006\007\b\t\n\011\012\r\014\015\016\017\018\019\020\021\022\023\024\025\026\027\028\029\030\031 !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\127\128\129\130\131\132\133\134\135\136\137\138\139\140\141\142\143\144\145\146\147\148\149\150\151\152\153\154\155\156\157\158\159\160\161\162\163\164\165\166\167\168\169\170\171\172\173\174\175\176\177\178\179\180\181\182\183\184\185\186\187\188\189\190\191\192\193\194\195\196\197\198\199\200\201\202\203\204\205\206\207\208\209\210\211\212\213\214\215\216\217\218\219\220\221\222\223\224\225\226\227\228\229\230\231\232\233\234\235\236\237\238\239\240\241\242\243\244\245\246\247\248\249\250\251\252\253\254\255" ? 

    */

    public String visit(TermImplChar t, Integer depth) {
        char c = t.value;
             if (c == '\'')   return "'\\''";
        else if (c == '\n')   return "'\\n'";
        else                  return "\'" + c + "\'";
    }

    public String visit(TermImplFloat t, Integer depth) {
        return "" + t.value;
    }

    public String visit(TermImplInt t, Integer depth) {
        return "" + t.value;
    }

    public String visit(TermImplString t, Integer depth) {
        return t.value;
    }

    public String visit(TermImplOpaque t, Integer depth) { 
        return "opaque" + t.hashCode();
    }

    private static final String preludeModuleName = "Prelude";
    private static final int consId
        = ModuleTable.sureGetId(preludeModuleName, ":");
    private static final int nilId
        = ModuleTable.sureGetId(preludeModuleName, "[]");
    private static final int closureId
        = ModuleTable.sureGetId(preludeModuleName, "closure");

    public String visit(TermImplUser t, Integer depth) {
        if (depth != null && depth.intValue() > 10) return "...";
        int root = t.getRoot();
	// TODO: the following hides the presence of "closure"
	// it may be a problem in debugging.
        if (closureId == root)
            return t.getArgument((byte) 0).getRepresentation().accept(this, depth);

        if (consId == root) {
            // it is a list
            Vector<TermImpl> listElements = flattenList(t);
            TermImpl tmp = listElements.firstElement();
            if (tmp instanceof TermImplChar) {
                // it is a list of characters, print as a string
                return ppString(listElements);
            } else {
                int lastElemRoot = listElements.lastElement().getRoot();
                if (lastElemRoot == nilId) {
                    // it is a finite list, print as [...]
                    return ppList(listElements, depth); 
                }           
            }
        }

        // No special list notation, even if it is a list
        String result = MapTable.getSymbol(root).symbolName;

        // if tuple just avoid the root symbol
        if (result.matches("\\([,]+\\)")) result = "";

        Term [] argument = t.getArgument();
        if (argument.length != 0) {
            boolean firstTime = true;
            Integer newDepth = depth==null ? 1 : depth+1;
            result += "(";
            for (Term term : argument) {
                if (! firstTime) result += ",";
                firstTime = false;
                result += term.getRepresentation().accept(this, newDepth);
            }
            result += ")";
        }
	if (identity) result = toHex(t)+result;
        return result;
    }

    public Vector<TermImpl> flattenList(TermImplUser t) {
        Vector<TermImpl> listElements = new Vector<TermImpl>();
        TermImpl u = t;
        while (u.getRoot() == consId) {
            listElements.add(u.getArgument((byte) 0).getRepresentation());
            u = u.getArgument((byte) 1).getRepresentation();
        }
        listElements.add(u);
        return listElements;
    }

    /** This method prints a list of characters as a string
     *  E.g., ['L','i','s','t'] is printed as "List" (with the quotes).
     *  This works well for normal form lists, hence for results.
     *  However, it does not work as well for lists that are
     *  not fully evaluated, which happens in traces.
     *  Thus, e.g., the list ['L','i',chr(115),'t'] is printed as "Li...".
     *  The list [chr(76)] is not printed by this method.
     */
    private String ppString(Vector<TermImpl> v) {
        // the root symbol of t is ":" 
        // the first element of the list is a literal character
        // print the argument as a string possibly ending in ...
        // if the end of the list contains non literal characters
        String result = "\"";
        for(TermImpl elem : v) {
            if (elem instanceof TermImplChar) {
                result += ((TermImplChar) elem).value;
            } else if (elem.getRoot() == nilId) {
                break;
            } else {
                result += "...";
                break;
            }
        }
        result += "\"";
        return result;
    }

    /** Print a non null list in the familiar notation,
     *  i.e., [e1,e2,...,en].  If the first element was a
     *  literal character, ppString has been called instead.
     *  The last element of the list is [], removed it
     */
    private String ppList(Vector<TermImpl> v, Integer depth) {
        v.removeElementAt(v.size()-1);
        boolean firstTime = true;
        Integer newDepth = depth==null ? 1 : depth+1;
        String result = "[";
        for(TermImpl elem : v) {
            if (! firstTime) result += ",";
            firstTime = false;
            result += elem.accept(this, newDepth);
        }
        result += "]";
        return result;
    }

    public String visit(Variable t, Integer depth) {
	String result = t.getName();
	if (identity) result = toHex(t)+result;
	return result;
    }

    private String toHex(Object object) {
	return "{"+Integer.toHexString(object.hashCode()).toUpperCase()+"}";
    }


}

