/**
 * Copyright (c) André Bargull
 * Alle Rechte vorbehalten / All Rights Reserved.  Use is subject to license terms.
 *
 * <https://github.com/anba/es6draft>
 */
package com.github.anba.es6draft.runtime.objects;

import com.github.anba.es6draft.runtime.Realm;
import com.github.anba.es6draft.runtime.internal.ScriptException;
import com.github.anba.es6draft.runtime.types.Intrinsics;
import com.github.anba.es6draft.runtime.types.Property;
import com.github.anba.es6draft.runtime.types.ScriptObject;
import com.github.anba.es6draft.runtime.types.Type;
import com.github.anba.es6draft.runtime.types.builtins.OrdinaryObject;

/**
 * <h1>19 Fundamental Objects</h1><br>
 * <h2>19.5 Error Objects</h2>
 * <ul>
 * <li>19.5.4 Properties of Error Instances
 * <li>19.5.6 NativeError Object Structure
 * <ul>
 * <li>19.5.6.4 Properties of NativeError Instances
 * </ul>
 * </ul>
 */
public final class ErrorObject extends OrdinaryObject {
    private final ScriptException exception;

    /**
     * Constructs a new Error object.
     * 
     * @param realm
     *            the realm object
     */
    public ErrorObject(Realm realm) {
        super(realm);
        this.exception = new ScriptException(this);
    }

    /**
     * Constructs a new Error object.
     * 
     * @param realm
     *            the realm object
     * @param prototype
     *            the error prototype
     * @param message
     *            the error message
     */
    public ErrorObject(Realm realm, Intrinsics prototype, String message) {
        super(realm, realm.getIntrinsic(prototype));
        this.exception = new ScriptException(this);
        defineErrorProperty("message", message, false);
    }

    /**
     * Constructs a new Error object.
     * 
     * @param realm
     *            the realm object
     * @param cause
     *            the exception's cause
     * @param prototype
     *            the error prototype
     * @param message
     *            the error message
     */
    public ErrorObject(Realm realm, Throwable cause, Intrinsics prototype, String message) {
        super(realm, realm.getIntrinsic(prototype));
        this.exception = new ScriptException(this, cause);
        defineErrorProperty("message", message, false);
    }

    /**
     * Constructs a new Error object.
     * 
     * @param realm
     *            the realm object
     * @param prototype
     *            the error prototype
     * @param message
     *            the error message
     * @param fileName
     *            the file name
     * @param lineNumber
     *            the line number
     * @param columnNumber
     *            the column number
     */
    public ErrorObject(Realm realm, Intrinsics prototype, String message, String fileName, int lineNumber,
            int columnNumber) {
        super(realm, realm.getIntrinsic(prototype));
        this.exception = new ScriptException(this);
        defineErrorProperty("message", message, false);
        defineErrorProperty("fileName", fileName, true);
        defineErrorProperty("lineNumber", lineNumber, true);
        defineErrorProperty("columnNumber", columnNumber, true);
    }

    /**
     * Constructs a new Error object.
     * 
     * @param realm
     *            the realm object
     * @param cause
     *            the exception's cause
     * @param prototype
     *            the error prototype
     * @param message
     *            the error message
     * @param fileName
     *            the file name
     * @param lineNumber
     *            the line number
     * @param columnNumber
     *            the column number
     */
    public ErrorObject(Realm realm, Throwable cause, Intrinsics prototype, String message, String fileName,
            int lineNumber, int columnNumber) {
        super(realm, realm.getIntrinsic(prototype));
        this.exception = new ScriptException(this, cause);
        defineErrorProperty("message", message, false);
        defineErrorProperty("fileName", fileName, true);
        defineErrorProperty("lineNumber", lineNumber, true);
        defineErrorProperty("columnNumber", columnNumber, true);
    }

    void defineErrorProperty(String name, Object value, boolean enumerable) {
        infallibleDefineOwnProperty(name, new Property(value, true, enumerable, true));
    }

    /**
     * Returns the wrapped {@link ScriptException} object.
     * 
     * @return the wrapped {@link ScriptException} object
     */
    public ScriptException getException() {
        return exception;
    }

    @Override
    public String className() {
        return "Error";
    }

    @Override
    public String toString() {
        String name = getErrorObjectProperty(this, "name", "Error");
        String message = getErrorObjectProperty(this, "message", "");
        if (name.length() == 0) {
            return message;
        }
        if (message.length() == 0) {
            return name;
        }
        return name + ": " + message;
    }

    /**
     * Specialized property retrieval to prevent any script execution.
     * 
     * @param error
     *            the error object
     * @param propertyName
     *            the property key
     * @param defaultValue
     *            the default value
     * @return property string value
     */
    private static String getErrorObjectProperty(ErrorObject error, String propertyName, String defaultValue) {
        Property property = error.lookupOwnProperty(propertyName);
        if (property == null) {
            ScriptObject proto = error.getPrototype();
            if (proto instanceof ErrorPrototype || proto instanceof NativeErrorPrototype) {
                property = ((OrdinaryObject) proto).lookupOwnProperty(propertyName);
            }
        }
        if (property == null || !property.isDataDescriptor()) {
            return defaultValue;
        }
        Object value = property.getValue();
        if (Type.isUndefined(value)) {
            return defaultValue;
        }
        // Prevent possible recursion
        if (Type.isObject(value)) {
            return String.format("<%s@%x>", value.getClass().getSimpleName(), System.identityHashCode(value));
        }
        return value.toString();
    }
}
