package org.jcon.df.column;

import org.jcon.param.Param;

/**
 * A ColumnDef is a column definition. It's purpose is to
 * provide a default value and validation of changes to
 * this value. This insures the state of the actual value
 * is always correct. A ColumnDef doesn't carry a value,
 * it only describes it.
 * <p>
 * A ColumnDef is designed to mirror a relational database
 * column's basic definition, plus provide additional
 * useful application parameter driven behavior.
 * <p>
 * This crucial abstraction allows the entity category to
 * be parameter driven right down to the column level. All
 * concrete column definitions extend this class.
 * <p>
 * Each Entity has one or more ColumnDefs. 
 * <p>
 * The defaultValue should never be null, and values
 * should never be null. This is because null cannot be
 * stored as an object. So the constructor of all subclasses
 * MUST set defaultValue = something.
 * <p>
 * Note - For the full DataFramework system, MIDColumn is
 * required and is ALWAYS the primary key and is ALWAYS
 * named "MID". Foreign key names should ALWAYS end with
 * "MID". This allows high readibility and simpler software
 * design. Two other columns are also required: TStamp
 * and LastUserSysMID, See the  ATSDR implmentation, where
 * this is done with includes in the Entity parex files.
 *
 * @author Jack Harich
 */

public abstract class ColumnDef implements java.io.Serializable {

//---------- Protected Fields ----------------------------
// These are required to be set when a column is created
protected String    name = "#Error#";
protected String    type = "#Error#";
protected String    displayName;

// These are not required to be set
protected boolean   required = false;
protected boolean   unique = false;
protected Object    defaultValue = "#Error#";
protected boolean   upperCase;
protected String    description;

protected static final String M_REQUIRED = " is required.";

//---------- Initialization ------------------------------
/**
 * No-arg to allow serialization. It does nothing.
 */
public ColumnDef() {
}
//---------- Superclass Overrides ------------------------
public String toString() {
    return name;
}
//---------- Properties ----------------------------------
/**
 * The type is our logical datatype, such as String. The
 * interned String is returned to allow "==".
 */
public String getType() {
    return type.intern();
}
/**
 * The standard for column names is: <pP <pre>
 * - Alphanumeric characters and underscores only.
 * - No spaces or periods.
 * - First letter is capitalized, like class names.
 * - Examples are AwardID, FirstName, Address2.
 * </pre> <p>
 * If displayName is null when the name is set, then the
 * displayName will be set to the name. This default
 * makes development easier and in many cases is correct.
 * <p>
 * The name is internally interned to allow the use of the
 * faster comparison operator (==) instead of equals().
 */
public void setName(String name) {
    if (name == null) {
        // Such as for settings, which have no name
        // Entity columnDefs always have a name
        this.name = null;
    } else {
        this.name = name.intern();
    }
    if (displayName == null) displayName = name;
}
public String getName() {
    return name;
}
/**
 * The display name is used in the user interface, such as
 * on screens and reports. It may contain spaces since
 * this enhances readability. The first letter in each
 * word should be capitalized, such as "Last Name".
 */
public void setDisplayName(String displayName) {
    this.displayName = displayName;
}
public String getDisplayName() {
    return displayName;
}
/**
 * If required is true then the column's value can never
 * be null. For a StringColumn this will be interprepted
 * to mean that a zero length string is the same as null.
 */
public void setRequired(boolean required) {
    this.required = required;
}
public boolean isRequired() {
    return required;
}
/**
 * If a column is required to be unique then no duplicates
 * are allowed. This makes unique fields good index or
 * primary key candidates.
 */
public void setUnique(boolean unique) {
    this.required = required;
}
public boolean isUnique() {
    return unique;
}
/**
 * When a new row is created the default value is used
 * as the initial value. Otherwise null is used, even for
 * numeric fields. *** Zero for numeric fields??? ***
 */
public void setDefaultValue(Object defaultValue) {
    this.defaultValue = defaultValue;
}
public Object getDefaultValue() {
    return defaultValue;
}
/**
 * If the UpperCase property is true then only upper case
 * will be valid. The default is false.
 */
public void setUpperCase(boolean upperCase) {
    this.upperCase = upperCase;
}
public boolean isUpperCase() {
    return upperCase;
}
/**
 * Sets the column description, which should be short, less
 * than about 100 characters. This has various uses, such
 * as documentation or the status bar text that will be
 * shown when the field has the focus. This property is
 * optional. If supplied it overrides any automatic column
 * description such as for CodeColumn.
 */
public void setDescription(String description) {
    this.description = description;
}
public String getDescription() {
    if (description != null) {
        return description;
    } else {
        return getAutomaticDescription();
    }
}
/**
 * Returns the preferred length, in characters, the column
 * perfers for its widget, such as a TextField. This does
 * not apply to all ColumnDefs, such as Boolean or Memo.
 * A value of zero means irrelevant or use the default.
 */
public int getPreferredLength() {
    return 0;
}
/**
 * This is a concise comma delimited list of important
 * properties. There is no space after the comma.
 */
public String getPropertySummary() {
    String summary = "";
    if (required) summary += "Req,";

    if (type.equals("String") || type.equals("Memo")) {
        if (! defaultValue.equals("")) {
            summary += "Default=" + defaultValue + ",";
        }
    } else if (type.equals("MID")) {
        // Skip default
    } else {
        summary += "Default=" + defaultValue + ",";
    }
    summary += getMyPropertySummary();
    // Remove last comma
    if (summary.length() > 0) summary = summary.substring(0, summary.length() - 1);
    return summary;
}
//---------- Public Methods ------------------------------
/**
 * Returns null if the value is okay, otherwise
 * returns a string explaining why the value is not
 * allowed.
 * <p>
 * This method is useful for validating the user's input.
 * The invalid message should be of the format (display
 * name) message, such as "Birth Date is required.".
 * <p>
 *      ********** Key Assumption ***********
 * NOTE - Currently value is always a String. This will
 * eventually change. Carrying values as strings GREATLY
 * reduces type conversions, such as in editing, parsing
 * SQL statements, and creating rowsets.
 * <p>
 * Note this calls the abstract template method
 * validateString(), after: handling nulls and required. <p> <pre>
 * - If null and not required, returns null
 * - If null and required, returns message
 * - If not a String, returns message
 * - If a string and required and empty, returns message
 * - Otherwise calls abstract validateString() </pre>
 */
public String validate(Object value) {

    if (value == null) {
        return (required ? displayName + M_REQUIRED : null);

    } else if (value instanceof String) {
        String text = (String)value;
        if (required && text.trim().length() == 0) {
            return displayName + M_REQUIRED;
        } else if (! required && text.trim().length() == 0) {
            return null; // Okay
        } else {
//print("ColumnDef - validate request for " + name +
//    " value = '" + text + "'");
//print("required=" + required + "  length=" + text.trim().length() );
            // Subclass must implement this
            return validateString(text);
        }
    } else if(this instanceof MIDColumn) {
        // Do nothing, can be null for new row
        return null;
    } else {
        return displayName + " must be a string.";
    }
}
//---------- Protected Methods ---------------------------
/**
 * Override to provide automatic description. Otherwise
 * returns null. This is a template method.
 */
protected String getAutomaticDescription() {
    return null;
}
protected void checkStandardProperties(Param param) {
    
    // These could be speeded up...
    //if (param.hasProperty("Required")) {
        setRequired(param.isTrueDefaultFalse("Required"));
    //}
    if (param.hasProperty("DisplayName")) {
        setDisplayName(param.getString("DisplayName"));
    }
    if (param.hasProperty("DefaultValue")) {
        setDefaultValue(param.getString("DefaultValue"));
    }
    //if (param.hasProperty("UpperCase")) {
        setUpperCase(param.isTrueDefaultFalse("UpperCase"));
    //}

    // Optimized
    //if (param.hasProperty("Description")) {
    setDescription(param.getString("Description"));
    //}
}
//---------- Abstract Methods ----------------------------
public abstract void   initFromParam(Param param);
public abstract String validateString(String value);
public abstract String getMyPropertySummary();

//---------- Private Methods -----------------------------
//--- Std
private void print(String text) {
    System.out.println("ColumnDef" + text);
}
} // End class
