package org.jcon.param.schema;

import org.jcon.param.Param;
import org.jcon.param.tree.Attribute;
import org.jcon.param.tree.Datum;
import org.jcon.param.tree.PropList;
import org.jcon.param.tree.PropListConverter;
import java.util.Enumeration;
import java.util.StringTokenizer;

/**
 * Defines the structure of a PropList Branch.
 *
 * @author Jack Harich
 */
public class PropListBranch extends Branch {

//---------- Private Fields ------------------------------

//---------- Abstract Implementation ---------------------
public String getBranchType() {
    return "PropList";
}
public String validate(Datum datum, boolean recurse) { // RECURSIVE
    PropList propList = (PropList)datum;
    Param propertiesParam = defParam.getParam("Properties");
    if (propertiesParam == null) return null;

    // Check each defined named property
    String[] names = propertiesParam.getDataKeys();
    for (int i = 0; i < names.length; i++) {

        String name = names[i];
        if (name == "BranchName") continue;

        //print(".validate() - Checking " + name);
        Param propParam = propertiesParam.getParam(name);
        Datum propDatum = propList.getDatum(name);

        boolean required = false;
        required = propParam.isTrueDefaultFalse("Required");
        
        if (propDatum == null) {
            if (required) {
                return "Required property '" + name +
                "' is missing " + getInvalidSuffix(datum);
            } else {
                continue; // Missing and not required
            }
        } else {
            String okay = validateProperty(
                datum, propDatum, propParam, recurse);
            if (okay != null) return okay;
        }
    }
    // Prevent datum properties not in def
    String extraName = preventExtraNames(propList, propertiesParam);
    if (extraName != null) {
        return "Property '" + extraName +
        "' is not allowed." + getInvalidSuffix(datum);
    }
    return null; // Okay
}
public Datum getDefault(Datum parentDatum) {
    if (defParam.hasProperty("Default")) {
        Param defaultParam = defParam.getParam("Default");
        return PropListConverter.toPropList(defaultParam);
    } else {
        return null;
    }
}
//---------- Private Methods -----------------------------
// Return extra name or null if all names are in param
// *** Later have option ExtraPropertiesAllowed = true
private String preventExtraNames(PropList propList, Param param) {
    Enumeration names = propList.getDatumNames();
    while (names.hasMoreElements()) {
        String name = (String)names.nextElement();
        if (name == "BranchName") continue;
        if (! param.hasProperty(name)) return name;
    }
    return null;
}
private String getInvalidSuffix(Datum datum) {
    return "\n\nIn '" + datum.getNamePath(true) +
    "' for DatumDef type '" + getName() + "'.";
}
private String validateProperty(Datum parentDatum,
        Datum datum, Param param, boolean recurse) {

    String type = param.getString("DatumType").intern();
    String okay = null;

    if (type == "Fact") {
        if (! (datum instanceof Attribute)) {
            return datum.getName() + " is not an Attribute " +
                getInvalidSuffix(parentDatum);
        }
        okay = validateAttribute(parentDatum, (Attribute)datum, param);

    } else if (type == "PropList") {
        if (recurse) {
            if (! (datum instanceof PropList)) {
                return datum.getName() + " is not a PropList " +
                    getInvalidSuffix(parentDatum);
            }
            String defType = param.getString("DefType");
            okay = schema.getBranch(defType).
                validate(datum, recurse); // RECURSE
        }
    } else {
        throw new IllegalArgumentException("Unknown " +
        "property Type '" + type + "' in " + getName());
    }
    return okay;
}
private String validateAttribute(Datum parentDatum,
        Attribute attribute, Param param) {

    //----- Prep
    boolean required = false;
    required = param.isTrueDefaultFalse("ValueRequired");

    // Value is "" or characters
    String value = attribute.getValue();
    if (value == null) value = "";
    value = value.trim().intern();

    //----- Validate
    if (value == "") {
        if (required) {
            return attribute.getName() + " is required " +
                getInvalidSuffix(parentDatum);
        } else {
            return null;
        }
    }
    // Value characters need validation using ValueType
    // *** Mod to handle large number ValueTypes *********
    String valueType = null;
    if (param.hasProperty("ValueType")) {
        valueType = param.getString("ValueType").intern();
    }
    String okay = null;

    if (valueType == "Name") {
        if (! nameIsValid(value)) {
            return attribute.getName() + " has invalid name '" +
                value + "' " + getInvalidSuffix(parentDatum);
        }
    } else if(valueType == "DelimitedNames") {
        if (! delimitedNamesIsValid(value)) {
            return attribute.getName() + " has an invalid delimited name in '" +
                value + "' " + getInvalidSuffix(parentDatum);
        }
    } else {
        throw new IllegalArgumentException("Unknown " +
        "ValueType '" + valueType + "' in " + getName());
    }
    return okay;
}
// Must contain no space, period, etc. Allow only letter,
// number, underscore, dollar sign.
private boolean nameIsValid(String name) {
    name = name.trim();
    char[] badChars = " .~`!@#%^&*()+=-{}|[]\\:;<>,?/".toCharArray();
    for (int i = 0; i < badChars.length; i++) {
        if (name.indexOf(badChars[i]) > -1) return false;
    }
    return true;
}
// Must be comma delimted list of names
private boolean delimitedNamesIsValid(String line) {
    StringTokenizer tokens = new StringTokenizer(line, ",");
    while (tokens.hasMoreTokens()) {
        String name = tokens.nextToken();
        if (! nameIsValid(name)) return false;
    }
    return true;
}
//--- Std
private static void print(String text) {
    System.out.println("PropListBranch" + text);
}

} // End class
