/*
 * TclBoolean.java
 *
 * Copyright (c) 1997 Sun Microsystems, Inc.
 *
 * See the file "license.terms" for information on usage and
 * redistribution of this file, and for a DISCLAIMER OF ALL
 * WARRANTIES.
 * 
 * RCS: @(#) $Id: TclBoolean.java,v 1.9 2006/06/13 06:52:47 mdejong Exp $
 *
 */

package tcl.lang;

/**
 * This class implements the boolean object type in Tcl.
 */

public class TclBoolean implements InternalRep {
    /**
     * Internal representations for all TclBooleans.
     */

    private final static TclBoolean trueRep = new TclBoolean(true);
    private final static TclBoolean falseRep = new TclBoolean(false);

    private final boolean value;

    /**
     * Construct a TclBoolean representation with the given boolean value.
     * Note that only two TclBoolean internal rep instances will ever
     * exist.
     *
     * @param b initial boolean value.
     */
    private TclBoolean(boolean b) {
	value = b;
    }

    /**
     * Returns a dupilcate of the current object.
     */
    public InternalRep duplicate() {
	return this;
    }

    /**
     * Implement this no-op for the InternalRep interface.
     */

    public void dispose() {}

    /**
     * Called to query the string representation of the Tcl object. This
     * method is called only by TclObject.toString() when
     * TclObject.stringRep is null.
     *
     * @return the string representation of the Tcl object.
     */
    public String toString() {
	if (value) {
	    return "1";
	} else {
	    return "0";
	}
    }

    /**
     * Creates a new instance of a TclObject with a TclBoolean internal
     * representation.
     *
     * @param b initial value of the boolean object.
     * @return the TclObject with the given boolean value.
     */

    public static TclObject newInstance(boolean b) {
	return new TclObject(b ? trueRep : falseRep);
    }

    /**
     * SetBooleanFromAny -> setBooleanFromAny
     *
     * Called to convert the other object's internal rep to boolean.
     *
     * @param interp current interpreter.
     * @param tobj the TclObject to convert to use the
     *     representation provided by this class.
     */
    private static void setBooleanFromAny(Interp interp, TclObject tobj)
	    throws TclException {
	// Get the string representation. Make it up-to-date if necessary.
	String string = tobj.toString();

	if (tobj.isIntType()) {
	    int i = TclInteger.get(interp, tobj);
	    if (i == 0) {
	        tobj.setInternalRep(falseRep);
	    } else {
	        tobj.setInternalRep(trueRep);
	    }

	    if (TclObject.saveObjRecords) {
	        String key = "TclInteger -> TclBoolean";
	        Integer num = (Integer) TclObject.objRecordMap.get(key);
	        if (num == null) {
	            num = new Integer(1);
	        } else {
	            num = new Integer(num.intValue() + 1);
	        }
	        TclObject.objRecordMap.put(key, num);
	    }
	} else if (tobj.isDoubleType()) {
	    double d = TclDouble.get(interp, tobj);
	    if (d == 0.0) {
	        tobj.setInternalRep(falseRep);
	    } else {
	        tobj.setInternalRep(trueRep);
	    }

	    if (TclObject.saveObjRecords) {
	        String key = "TclDouble -> TclBoolean";
	        Integer num = (Integer) TclObject.objRecordMap.get(key);
	        if (num == null) {
	            num = new Integer(1);
	        } else {
	            num = new Integer(num.intValue() + 1);
	        }
	        TclObject.objRecordMap.put(key, num);
	    }
	} else {
	    // Copy the string converting its characters to lower case.

	    string = string.toLowerCase();
	    String lowerCase = string.toLowerCase();

	    // Parse the string as a boolean. We use an implementation here that
	    // doesn't report errors in interp if interp is null.

	    boolean b;
	    boolean badBoolean = false;

	    try {
	        b = Util.getBoolean(interp, lowerCase);
	    } catch (TclException te) {
	        // Boolean values can be extracted from ints or doubles.  Note
	        // that we don't use strtoul or strtoull here because we don't
	        // care about what the value is, just whether it is equal to
	        // zero or not.

                badBoolean = true;
                b = false; // Always reassigned below
                if (interp != null) {
                    interp.resetResult();
                }

	        try {
                    b = (Util.getInt(interp, lowerCase) != 0);
                    badBoolean = false;
	        } catch (TclException te2) {}

                if (badBoolean) {
	            try {
                        b = (Util.getDouble(interp, lowerCase) != 0.0);
                        badBoolean = false;
	            } catch (TclException te2) {}
                }
	    }
            if (badBoolean) {
	        if (interp != null) {
	            interp.resetResult();
	        }
	        throw new TclException(interp,
	            "expected boolean value but got \"" +
	            string + "\"");
            }
	    if (b) {
	        tobj.setInternalRep(trueRep);
	    } else {
	        tobj.setInternalRep(falseRep);
	    }

	    if (TclObject.saveObjRecords) {
	        String key = "TclString -> TclBoolean";
	        Integer num = (Integer) TclObject.objRecordMap.get(key);
	        if (num == null) {
	            num = new Integer(1);
	        } else {
	            num = new Integer(num.intValue() + 1);
	        }
	        TclObject.objRecordMap.put(key, num);
	    }
	}
    }

    /**
     * Returns the value of the object as an boolean.
     *
     * An object with a TclBoolean internal rep has
     * a boolean value. An object with a TclInteger
     * internal rep and has the int value "0" or "1"
     * is also a valid boolean value.
     *
     * @param interp current interpreter.
     * @param tobj the TclObject to use as an boolean.
     * @return the boolean value of the object.
     * @exception TclException if the object cannot be converted into a
     *     boolean.
     */
    public static boolean get(
	final Interp interp,
	final TclObject tobj)
	    throws TclException
    {
	if (tobj.isIntType()) {
	    // An integer with the value 0 or 1 can be
	    // considered a boolean value, so there is
	    // no need to change the internal rep.
	    int ival = tobj.ivalue;  // Inline TclInteger.get()
	    if (ival == 0) {
	        return false;
	    } else if (ival == 1) {
	        return true;
	    }
	}

	InternalRep rep = tobj.getInternalRep();
	TclBoolean tbool;

	if (!(rep instanceof TclBoolean)) {
	    setBooleanFromAny(interp, tobj);
	    tbool = (TclBoolean) tobj.getInternalRep();
	} else {
	    tbool = (TclBoolean) rep;
	}
	return tbool.value;
    }
}

