// ----------------------------------------------------------------------------
// Copyright 2007-2017, GeoTelematic Solutions, Inc.
// All rights reserved
// ----------------------------------------------------------------------------
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// 
// http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// ----------------------------------------------------------------------------
// Change History:
//  2008/05/14  Martin D. Flynn
//     -Initial release
//  2010/11/29  Martin D. Flynn
//     -Added "getPrivateLabelPropertiesForHost" for custom PrivateLabel properties.
//  2012/02/03  Martin D. Flynn
//     -Increased "resourceID" key size from 64 to 80.
//  2013/04/08  Martin D. Flynn
//     -Added "getGlobalResourceValue"/"setGlobalResourceValue"
// ----------------------------------------------------------------------------
package org.opengts.db.tables;

import java.lang.*;
import java.util.*;
import java.math.*;
import java.io.*;
import java.sql.*;

import org.opengts.util.*;
import org.opengts.dbtools.*;

import org.opengts.db.*;

public class Resource
    extends AccountRecord<Resource>
{

    // ------------------------------------------------------------------------

    /* Temporary properties */
    private static final String RESID_TemporaryProperties       = "temporaryproperties";

    /**
    *** Get temporary properties for account
    **/
    public static RTProperties getTemporaryProperties(Account account)
    {
        if (account != null) {
            try {
                Resource res = Resource.getResource(account, Resource.RESID_TemporaryProperties);
                return (res != null)? res.getRTProperties() : null;
            } catch (DBException dbe) {
                Print.logError("Error reading Account resource: " + dbe);
                return null;
            }
        } else {
            return null;
        }
    }

    /**
    *** Update temporary properties for account
    **/
    public static void updateTemporaryProperties(Account account, String props)
    {
        if (account != null) {
            try {
                Resource res = Resource.getResource(account, Resource.RESID_TemporaryProperties);
                if (StringTools.isBlank(props)) {
                    // -- clear temporary properties
                    if ((res != null) && !StringTools.isBlank(res.getProperties())) {
                        res.setProperties("");
                        res.update(Resource.FLD_properties);
                    } else {
                        // -- no change
                    }
                } else {
                    if (res != null) {
                        if (!props.equals(res.getProperties())) {
                            // -- update existing temporary properties
                            res.setProperties(props);
                            res.update(Resource.FLD_properties);
                        } else {
                            // -- no change
                        }
                    } else {
                        // -- create new temporary properties entry
                        res = Resource.getResource(account, Resource.RESID_TemporaryProperties, true, TYPE_RTPROPS);
                        res.setType(Resource.TYPE_RTPROPS);
                        res.setProperties(props);
                        res.save();
                    }
                }
            } catch (DBException dbe) {
                Print.logException("Unable to save Resource: " + account.getAccountID(), dbe);
            }
        }
    }

    // ------------------------------------------------------------------------

    /* common resource-ids */
    public  static final String RESID_PrivateLabel_Properties_  = "privatelabel.properties:";

    // ------------------------------------------------------------------------
    // ------------------------------------------------------------------------
    // Resource types

    public static final String TYPE_TEXT                    = "text";               // value is text
    public static final String TYPE_XML                     = "xml";                // value is XML
    public static final String TYPE_HTML                    = "html";               // value is HTML
    public static final String TYPE_LONG                    = "long";               // value is Long
    public static final String TYPE_BINARY                  = "binary";             // value is opague binary
    public static final String TYPE_RTPROPS                 = "rtprops";            // value is RTProperties string
    public static final String TYPE_COLOR                   = "color";              // value is a color representation

    public static final String TYPE_URL                     = "url";                // value is a URL
    public static final String TYPE_URL_                    = TYPE_URL  + "/";
    public static final String TYPE_URL_IMAGE               = TYPE_URL_ + "image";  // value is a image URL
    public static final String TYPE_URL_PDF                 = TYPE_URL_ + "pdf";    // value is a PDF URL

    public static final String TYPE_IMAGE_                  = "image/";
    public static final String TYPE_IMAGE_JPEG              = TYPE_IMAGE_ + "jpeg"; // value is a binary JPEG
    public static final String TYPE_IMAGE_GIF               = TYPE_IMAGE_ + "gif";  // value is a binary GIF
    public static final String TYPE_IMAGE_PNG               = TYPE_IMAGE_ + "png";  // value is a binary PNG
    public static final String TYPE_IMAGE_GENERIC           = TYPE_IMAGE_ + "generic";

    /** 
    *** Returns true if the specified type represents a String
    *** @param type  The specified type
    *** @return True if the specified represents a String value
    **/
    public static boolean isStringType(String type)
    {

        /* blank type */
        if (StringTools.isBlank(type)) {
            return false;
        }
        type = type.toLowerCase();

        /* check explicit String types */
        if (type.startsWith(TYPE_TEXT)    ||
            type.startsWith(TYPE_XML)     ||
            type.startsWith(TYPE_HTML)    ||
            type.startsWith(TYPE_URL)     ||
            type.startsWith(TYPE_RTPROPS) ||
            type.startsWith(TYPE_COLOR)     ) {
            return true;
        }

        /* not explicit String */
        return false;

    }

    /** 
    *** Returns true if the specified type represents a Long
    *** @param type  The specified type
    *** @return True if the specified represents a Long value
    **/
    public static boolean isLongType(String type)
    {

        /* blank type */
        if (StringTools.isBlank(type)) {
            return false;
        }
        type = type.toLowerCase();

        /* check explicit String types */
        if (type.startsWith(TYPE_LONG)) {
            return true;
        }

        /* not explicit String */
        return false;

    }

    /** 
    *** Returns true if the specified type represents a Binary value
    *** @param type  The specified type
    *** @return True if the specified represents a Binary value
    **/
    public static boolean isBinaryType(String type)
    {

        /* blank type */
        if (StringTools.isBlank(type)) {
            return true; // default to binary
        }
        type = type.toLowerCase();

        /* check explicit Binary types */
        if (type.startsWith(TYPE_IMAGE_) ||
            type.startsWith(TYPE_BINARY)   ) {
            return true;
        }

        /* not explicit Binary */
        return false;

    }

    // ------------------------------------------------------------------------

    /* properties */
    public static final String PROP_WIDTH                   = "width";      // image url width
    public static final String PROP_HEIGHT                  = "height";     // image url height
    public static final String PROP_ICON_URL                = "iconURL";    // thumbnail icon
    public static final String PROP_ICON_WIDTH              = "iconWidth";  // thumbnail icon width
    public static final String PROP_ICON_HEIGHT             = "iconHeight"; // thumbnail icon height
    
    // ------------------------------------------------------------------------
    // ------------------------------------------------------------------------

    /* local 'Dimension' definition (instead of the 'Dimension' in awt) */
    public static class Dimension
    {
        public int width  = 0; // public
        public int height = 0; // public
        public Dimension(int w, int h) {
            this.width  = w;
            this.height = h;
        }
        public int getWidth() {
            return this.width;
        }
        public int getHeight() {
            return this.height;
        }
    }

    // ------------------------------------------------------------------------
    // ------------------------------------------------------------------------
    // SQL table definition below

    /* table name */
    private static final String _TABLE_NAME                 = "Resource";
    public static String TABLE_NAME() { return DBProvider._preTranslateTableName(_TABLE_NAME); }

    /* field definition */
    public static final String FLD_resourceID               = "resourceID";
    public static final String FLD_type                     = "type";
    public static final String FLD_title                    = "title";
    public static final String FLD_properties               = "properties";
    public static final String FLD_value                    = "value";
    private static DBField FieldInfo[] = {
        // -- Resource fields
        newField_accountID(true),
        new DBField(FLD_resourceID    , String.class, DBField.TYPE_STRING(80), "Resource ID", "key=true editor=accountString"),
        new DBField(FLD_type          , String.class, DBField.TYPE_STRING(16), "Type"       , "edit=2"),
        new DBField(FLD_title         , String.class, DBField.TYPE_STRING(70), "Title"      , "edit=2 utf8=true"),
        new DBField(FLD_properties    , String.class, DBField.TYPE_TEXT      , "Properties" , "edit=2"),
        new DBField(FLD_value         , byte[].class, DBField.TYPE_BLOB      , "Value"      , "edit=2"),
        // -- Common fields
        newField_displayName(),
        newField_description(),
        newField_lastUpdateTime(),
        newField_lastUpdateAccount(true),
        newField_lastUpdateUser(true),
        newField_creationTime(),
    };

    /* key class */
    public static class Key
        extends AccountKey<Resource>
    {
        public Key() {
            super();
        }
        public Key(String acctId, String strKey) {
            super.setKeyValue(FLD_accountID , ((acctId != null)? acctId.toLowerCase() : ""));
            super.setKeyValue(FLD_resourceID, ((strKey != null)? strKey               : ""));
        }
        public DBFactory<Resource> getFactory() {
            return Resource.getFactory();
        }
    }

    /* factory constructor */
    private static DBFactory<Resource> factory = null;
    public static DBFactory<Resource> getFactory()
    {
        if (factory == null) {
            factory = DBFactory.createDBFactory(
                Resource.TABLE_NAME(), 
                Resource.FieldInfo, 
                DBFactory.KeyType.PRIMARY,
                Resource.class, 
                Resource.Key.class,
                true/*editable*/, true/*viewable*/);
            factory.addParentTable(Account.TABLE_NAME());
        }
        return factory;
    }

    /* Bean instance */
    public Resource()
    {
        super();
    }

    /* database record */
    public Resource(Resource.Key key)
    {
        super(key);
    }

    // ------------------------------------------------------------------------

    /* table description */
    public static String getTableDescription(Locale loc)
    {
        I18N i18n = I18N.getI18N(Resource.class, loc);
        return i18n.getString("Resource.description", 
            "This table defines " +
            "Account specific text resources."
            );
    }

    // SQL table definition above
    // ------------------------------------------------------------------------
    // ------------------------------------------------------------------------
    // Bean access fields below

    /* return the Resource ID for this record */
    public String getResourceID()
    {
        String v = (String)this.getFieldValue(FLD_resourceID);
        return StringTools.trim(v);
    }
    
    /* set the Resource ID for this record */
    private void setResourceID(String v)
    {
        this.setFieldValue(FLD_resourceID, StringTools.trim(v));
    }

    // ------------------------------------------------------------------------

    /* return the resource title */
    public String getTitle()
    {
        String v = (String)this.getFieldValue(FLD_title);
        return StringTools.trim(v);
    }

    /* set the resource title */
    public void setTitle(String v)
    {
        this.setFieldValue(FLD_title, StringTools.trim(v));
    }

    // ------------------------------------------------------------------------

    /* return the resource type */
    public String getType()
    {
        String v = (String)this.getFieldValue(FLD_type);
        return StringTools.trim(v);
    }

    /* set the resource type */
    public void setType(String v)
    {
        this.setFieldValue(FLD_type, StringTools.trim(v));
    }

    /* return true is the resource prepresents a String type */
    public boolean isStringType()
    {
        String t = this.getType();
        return Resource.isStringType(t);
    }

    /* return true is the resource prepresents a Long type */
    public boolean isLongType()
    {
        String t = this.getType();
        return Resource.isLongType(t);
    }

    /* return true is the resource prepresents a Binary type */
    public boolean isBinaryType()
    {
        String t = this.getType();
        return Resource.isBinaryType(t);
    }

    /* return true if resource is a URL */
    public boolean isURL()
    {
        String t = this.getType();
        if (t.equals(TYPE_URL) || t.startsWith(TYPE_URL_)) {
            return true;
        } else {
            return false;
        }
    }

    /* return true if resource is an image URL */
    public boolean isImageURL()
    {
        String t = this.getType();
        if (t.equalsIgnoreCase(TYPE_URL_IMAGE)) {
            return true;
        } else {
            return false;
        }
    }

    /* return true if resource is a binary image */
    public boolean isImage()
    {
        String t = this.getType();
        if (t.startsWith(TYPE_IMAGE_)) {
            return true;
        } else {
            return false;
        }
    }

    /* returns true if resource is a properties type */
    public boolean isRTProperties()
    {
        String t = this.getType();
        if (t.startsWith(TYPE_RTPROPS)) {
            return true;
        } else {
            return false;
        }
    }

    // ------------------------------------------------------------------------
    
    private RTProperties rtProps = null;

    /* return the resource properties */
    public String getProperties()
    {
        String v = (String)this.getFieldValue(FLD_properties);
        //Print.logStackTrace("Properties: " + v);
        return StringTools.trim(v);
    }

    /* sets the resource type */
    public void setProperties(String v)
    {
        this._setProperties(v);
        this.rtProps = null;
    }

    /* sets the resource type */
    protected void _setProperties(String v)
    {
        this.setFieldValue(FLD_properties, StringTools.trim(v));
    }

    /* sets the resource type */
    public void setRTProperties(RTProperties v)
    {
        this._setProperties((v != null)? v.toString() : "");
        this.rtProps = null;
    }

    /* return the resource properties, bound to this Resource */
    public RTProperties getRTProperties()
    {
        if (this.rtProps == null) {
            this.rtProps = this._getRTProperties();
            // -- add listener to update FLD_properties when the RTProperties instance is changed
            this.rtProps.addChangeListener(new RTProperties.PropertyChangeListener() {
                public void propertyChange(RTProperties.PropertyChangeEvent pce) {
                    //Print.logInfo("Updating RTProperties: " + pce.getKey());
                    Resource.this._setProperties(Resource.this.rtProps.toString());
                }
            });
        }
        return this.rtProps;
    }

    /* creates/return the resource properties */
    public RTProperties _getRTProperties()
    {
        RTProperties rtp = new RTProperties();
        rtp.setProperties(this.getProperties(), false);
        return rtp;
    }

    /* get a specific property value */
    public String getProperty(String key, String dft)
    {
        return this.getRTProperties().getString(key, dft);
    }

    /* get a specific property value */
    public int getProperty(String key, int dft)
    {
        return this.getRTProperties().getInt(key, dft);
    }

    /* get a specific property value */
    public long getProperty(String key, long dft)
    {
        return this.getRTProperties().getLong(key, dft);
    }

    /* get a specific property value */
    public double getProperty(String key, double dft)
    {
        return this.getRTProperties().getDouble(key, dft);
    }

    /* get the property width/height */
    public Resource.Dimension getDimension(int dftWidth, int dftHeight)
    {
        int w = this.getProperty(Resource.PROP_WIDTH , dftWidth );
        int h = this.getProperty(Resource.PROP_HEIGHT, dftHeight);
        return new Resource.Dimension(w, h);
    }

    /* get the property icon URL */
    public String getIconURL(String dft)
    {
        return this.getProperty(Resource.PROP_ICON_URL, dft);
    }

    /* get the property icon width/height */
    public Resource.Dimension getIconDimension(int dftWidth, int dftHeight)
    {
        int w = this.getProperty(Resource.PROP_ICON_WIDTH , dftWidth );
        int h = this.getProperty(Resource.PROP_ICON_HEIGHT, dftHeight);
        return new Resource.Dimension(w, h);
    }

    // ------------------------------------------------------------------------

    /* return the resource value */
    public byte[] getValue()
    {
        byte v[] = (byte[])this.getFieldValue(FLD_value);
        return (v != null)? v : new byte[0];
    }

    /* set the resource value */
    public void setValue(byte[] v)
    {
        this.setFieldValue(FLD_value, ((v != null)? v : new byte[0]));
    }

    /* sets the resource value */
    public void setValue(Object v) 
    {
        if (v == null) {
            this.setValue((byte[])null);
        } else
        if (v instanceof byte[]) {
            this.setValue((byte[])v);
        } else
        if (v instanceof String) {
            this.setValue(StringTools.getBytes((String)v));
        } else
        if (v instanceof Long) {
            this.setValue(StringTools.getBytes(v.toString()));
        } else {
            this.setValue(StringTools.getBytes(v.toString()));
        }
    }

    // --------------------------------

    /* set the resource value as a String */
    public void setStringValue(String v)
    {
        this.setValue(StringTools.getBytes(v));
    }

    /* get the resource value as a String */
    public String getStringValue()
    {
        byte v[] = this.getValue();
        if (this.isBinaryType()) {
            return "0x" + StringTools.toHexString(v);
        } else
        if (this.isRTProperties()) {
            return this.getProperties();
        } else {
            return StringTools.toStringValue(v);
        }
    }

    // Bean access fields above
    // ------------------------------------------------------------------------
    // ------------------------------------------------------------------------

    /* overridden to set default values */
    public void setCreationDefaultValues(String type)
    {
        this.setDescription("Resource Text");
        if (StringTools.isBlank(type)) {
            this.setType(Resource.TYPE_TEXT);
            this.setStringValue("");
        } else
        if (type.equals(TYPE_LONG)) {
            this.setType(Resource.TYPE_LONG);
            this.setStringValue("0");
        } else {
            this.setType(type);
            this.setStringValue("");
        }
        super.setRuntimeDefaultValues();
    }

    // ------------------------------------------------------------------------

    /* return the AccountID/ResourceID */
    public String toString()
    {
        return this.getAccountID() + "/" + this.getResourceID();
    }

    // ------------------------------------------------------------------------
    // ------------------------------------------------------------------------

    public static boolean exists(String acctID, String resID)
        throws DBException // if error occurs while testing existance
    {
        if ((acctID != null) && (resID != null)) {
            Resource.Key resKey = new Resource.Key(acctID, resID);
            return resKey.exists();
        }
        return false;
    }
    
    // ------------------------------------------------------------------------

    /* get Resource */
    // -- may return null
    protected static Resource _getResource(String acctID, String resID)
        throws DBException
    {
        if ((acctID != null) && (resID != null)) {
            Resource.Key key = new Resource.Key(acctID, resID);
            if (key.exists()) {
                Resource res = key.getDBRecord(true);
                return res;
            } else {
                // Resource does not exist
                return null;
            }
        } else {
            return null; // just say it doesn't exist
        }
    }

    /* get Resource */
    public static Resource getResource(Account account, String resID)
        throws DBException
    {
        if (account != null) {
            String acctID = account.getAccountID();
            Resource res = Resource._getResource(acctID, resID);
            if (res != null) {
                res.setAccount(account);
                return res;
            } else {
                //Print.logWarn("Resource-ID not found: " + resID);
                return null;
            }
        } else {
            return null;
        }
    }

    // --------------------------------

    /* get global Resource */
    public static Resource getGlobalResource(String resID)
        throws DBException
    {
        String sysAdminID = AccountRecord.getSystemAdminAccountID();
        Resource res = Resource._getResource(sysAdminID, resID);
        if (res != null) {
            //res.setAccount(account);
            return res;
        } else {
            //Print.logWarn("Global Resource-ID not found: " + resID);
            return null;
        }
    }

    /**
    *** Gets a global resource value (owned by the "sysadmin" account)
    **/
    public static Object getGlobalResourceValue(String resID, Object dft)
        throws DBException
    {
        String sysAdminID = AccountRecord.getSystemAdminAccountID();
        Resource res = Resource._getResource(sysAdminID, resID);
        if (res != null) {
            if (res.isRTProperties()) {
                return res._getRTProperties();
            } else
            if (res.isStringType()) {
                return res.getStringValue();
            } else
            if (res.isLongType()) {
                String valStr = res.getStringValue();
                if (StringTools.isLong(valStr,false)) {
                    return new Long(StringTools.parseLong(res.getStringValue(),-1L));
                } else {
                    return dft;
                }
            } else {
                return res.getValue();
            }
        } else {
            return dft;
        }
    }

    // --------------------------------

    /**
    *** Gets the next likely-unique sequence number from the specified resourceID
    **/
    public static long getGlobalNextSequence(String resID, long dft, long initVal)
        throws DBException
    {
        String sysAdminID = AccountRecord.getSystemAdminAccountID();
        String type       = TYPE_LONG;
        String desc       = "Sequence#";

        /* loop until it is unique */
        for (int i = 0; i < 2; i++) {
            long nextSeq;

            /* resource already exists? */
            boolean exists = Resource.exists(sysAdminID, resID);
            if (exists) {
                // -- read/increment existing sequence#
                Resource res = Resource._getResource(sysAdminID, resID, false, null); // not null
                if (res.getType().equalsIgnoreCase(type)) {
                    // -- get/update
                    long seq = StringTools.parseLong(res.getStringValue(),dft);
                    nextSeq = seq + 1;
                    res.setValue(String.valueOf(nextSeq));
                    res.update(FLD_value);
                } else {
                    Print.logWarn("Existing Resource is not type '"+type+"'");
                    return dft;
                }
            } else {
                // -- new sequence#
                nextSeq = initVal;
                String value = String.valueOf(nextSeq);
                Resource._setResource(sysAdminID, resID, type, desc, value);
            }
    
            /* read it again and see if it is what we are expecting */
            Resource res = Resource._getResource(sysAdminID, resID, false, null); // not null
            long seq = StringTools.parseLong(res.getStringValue(),0L);
            if (nextSeq == seq) {
                return nextSeq;
            }

        }

        /* still not unique, return default */
        return dft;


    }

    /**
    *** Gets the next likely-unique sequence number from the specified resourceID
    **/
    public static long getGlobalNextSequence(String resID, long dft)
        throws DBException
    {
        return Resource.getGlobalNextSequence(resID, dft, 1L);
    }
    
    // --------------------------------

    /**
    *** Gets the next likely-unique sequence number from the specified resourceID
    **/
    public static int getGlobalNextSequence(String resID, int dft, int initVal)
        throws DBException
    {
        return (int)Resource.getGlobalNextSequence(resID, (long)dft, (long)initVal);
    }

    /**
    *** Gets the next likely-unique sequence number from the specified resourceID
    **/
    public static int getGlobalNextSequence(String resID, int dft)
        throws DBException
    {
        return (int)Resource.getGlobalNextSequence(resID, (long)dft);
    }

    // ------------------------------------------------------------------------

    /* get Resource */
    // Note: does NOT return null
    public static Resource getResource(Account account, String resID, boolean create, String type)
        throws DBException
    {

        /* account-id specified? */
        if (account == null) {
            throw new DBNotFoundException("Account not specified.");
        }
        String acctID = account.getAccountID();

        /* return Resource */
        // -- not yet saved if created
        Resource res = Resource._getResource(acctID, resID, create, type);
        res.setAccount(account);
        return res;

    }

    /* get Resource */
    // Note: does NOT return null
    protected static Resource _getResource(String acctID, String resID, boolean create, String type)
        throws DBException
    {

        /* check account-id */
        if (acctID == null) {
            throw new DBNotFoundException("Account-ID not specified");
        }

        /* Resource-id specified? */
        if (resID == null) {
            throw new DBNotFoundException("Resource-ID not specified for account: " + acctID);
        }

        /* get/create */
        Resource res = null;
        Resource.Key resKey = new Resource.Key(acctID, resID);
        if (!resKey.exists()) {
            if (create) {
                res = resKey.getDBRecord();
              //res.setAccount(account);
                res.setCreationDefaultValues(type);
                return res; // not yet saved!
            } else {
                throw new DBNotFoundException("Resource-ID does not exists: " + resKey);
            }
        } else
        if (create) {
            // -- we've been asked to create the Resource, and it already exists
            throw new DBAlreadyExistsException("Resource-ID already exists '" + resKey + "'");
        } else {
            res = Resource._getResource(acctID, resID);
            if (res == null) {
                throw new DBException("Unable to read existing Resource-ID: " + resKey);
            }
            return res;
        }
        
    }

    /**
    *** Create new Resource with key
    *** will throw DBException if resource key already exists
    *** @param account  The Account
    *** @param resID    The Resource-ID
    *** @throws DBException if Account/Resource-ID already exists
    **/
    public static Resource createNewResource(Account account, String resID, String type)
        throws DBException
    {
        if ((account != null) && !StringTools.isBlank(resID)) {
            Resource res = Resource.getResource(account, resID, true, type); // does not return null
            res.save();
            return res;
        } else {
            throw new DBException("Invalid Account/ResourceID specified");
        }
    }

    // ------------------------------------------------------------------------

    /**
    *** Creates/Set the specified Resourse
    *** @param account  The Account
    *** @param resID    The Resource-ID
    *** @param type     The Resource type
    *** @param desc     The Resource description
    *** @param value    The Resource value
    *** @return True if there was an existing resource which was overwritten
    **/
    public static boolean setResource(Account account, String resID, String type, String desc, Object value)
        throws DBException
    {

        /* no account */
        if (account == null) {
            throw new DBException("Account not specified");
        }
        String acctID = account.getAccountID();

        /* set/create resource */
        return Resource._setResource(acctID, resID, type, desc, value);

    }

    /**
    *** Creates/Sets the specified Resourse
    *** @param acctID   The Account-ID
    *** @param resID    The Resource-ID
    *** @param type     The Resource type
    *** @param desc     The Resource description
    *** @param value    The Resource value
    *** @return True if there was an existing resource which was overwritten
    **/
    protected static boolean _setResource(String acctID, String resID, String type, String desc, Object value)
        throws DBException
    {

        /* no resource ID */
        if (StringTools.isBlank(resID)) {
            throw new DBException("Invalid ResourceID specified for Account: " + acctID);
        }

        /* resource already exists? */
        boolean exists = Resource.exists(acctID, resID);
        Resource res = Resource._getResource(acctID, resID, !exists, type); // not null

        /* type */
        if ((type != null) && !type.equals(res.getType())) {
            res.setType(type);
        }

        /* title */
        if (desc != null) {
            res.setTitle(desc);
        }

        /* set value */
        if (value instanceof RTProperties) {
            res.setRTProperties((RTProperties)value);
        } else {
            res.setValue(value);
        }

        /* save */
        res.save();

        /* return */
        return exists;

    }

    /**
    *** Creates/Sets a global resource (owned by the "sysadmin" account)
    **/
    public static boolean setGlobalResourceValue(String resID, String type, String desc, Object value)
        throws DBException
    {
        String sysAdminID = AccountRecord.getSystemAdminAccountID();
        return Resource._setResource(sysAdminID, resID, type, desc, value);
    }

    // ------------------------------------------------------------------------
    // ------------------------------------------------------------------------

    /* return list of all Resource keys owned by the specified Account (NOT SCALABLE) */
    // does not return null
    public static OrderedSet<String> getResourcesForAccount(String acctId, String startsWith)
        throws DBException
    {

        /* invalid account */
        if (StringTools.isBlank(acctId)) {
            return new OrderedSet<String>();
        }

        /* select */
        // DBSelect: [SELECT] WHERE <Where> ORDER BY resourceID
        DBSelect<Resource> dsel = new DBSelect<Resource>(Resource.getFactory());
        dsel.setSelectedFields(FLD_resourceID);
        dsel.setOrderByFields(FLD_resourceID);
        DBWhere dwh = dsel.createDBWhere();
        if (StringTools.isBlank(startsWith)) {
            dsel.setWhere(dwh.WHERE(
                dwh.EQ(Resource.FLD_accountID,acctId)
            ));
        } else {
            dsel.setWhere(dwh.WHERE_(
                dwh.AND(
                    dwh.EQ(Resource.FLD_accountID,acctId),
                    dwh.STARTSWITH(Resource.FLD_resourceID,startsWith)
                )
            ));
        }

        /* return list */
        return Resource.getResources(dsel);

    }
    
    /* return list of Resources based on DBSelect (NOT SCALABLE) */
    // does not return null
    public static OrderedSet<String> getResources(DBSelect<Resource> dsel)
        throws DBException
    {
        OrderedSet<String> resourceList = new OrderedSet<String>(true);

        /* invalid account */
        if (dsel == null) {
            return resourceList;
        }

        /* get record ids */
        DBConnection dbc = null;
        Statement   stmt = null;
        ResultSet     rs = null;
        try {

            /* get record ids */
            dbc  = DBConnection.getDBConnection_read();
            stmt = dbc.execute(dsel.toString());
            rs   = stmt.getResultSet();
            while (rs.next()) {
                String resourceId = rs.getString(Resource.FLD_resourceID);
                resourceList.add(resourceId);
            }

        } catch (SQLException sqe) {
            throw new DBException("Getting Account Resource List", sqe);
        } finally {
            if (rs   != null) { try { rs.close();   } catch (Throwable t) {} }
            if (stmt != null) { try { stmt.close(); } catch (Throwable t) {} }
            DBConnection.release(dbc);
        }

        /* return list */
        return resourceList;

    }

    // ------------------------------------------------------------------------
    // ------------------------------------------------------------------------
    
    private static void MoveProperty(RTProperties rtProps, String fromKey, String toKey)
    {
        if (rtProps.hasProperty(fromKey)) {
            rtProps.setString(toKey, rtProps.getString(fromKey,""));
            rtProps.removeProperty(fromKey);
        }
    }
    
    /**
    *** Gets the custom property resources for the specified hostname
    *** @return The Resource RTProperties, or null if the properties are not found
    **/
    public static RTProperties getPrivateLabelPropertiesForHost(String hostName, String urlPath)
    {
        // Example properties:
        //   PageTitle=GPS Tracking
        //   Copyright=Copyright GPS Tracking
        //   banner.width=860
        //   banner.style="border-bottom: 1px solid black;"
        //   banner.imageSource=custom/TrackingBanner.jpg
        //   banner.imageWidth=860
        //   banner.imageHeight=120
        //   banner.anchorLink=
        //   Background.color=#FFFFFF
        //   Background.image=url(./extra/images/Banner_GPSSatShadowShort.png)
        //   Background.position=center
        //   Background.repeat=no-repeat

        /* no host name? */
        if (StringTools.isBlank(hostName)) {
            return null;
        }

        /* "sysadmin" name not defined? */
        String sysAdminID = AccountRecord.getSystemAdminAccountID();
        if (StringTools.isBlank(sysAdminID)) {
            // -- will(should) not occur
            return null;
        }

        /* wildcard host name */
        // -- "gps.example.com" ==> "example.com"
        String wcHostName;
        { // local scope
            int w = hostName.indexOf(".");
            wcHostName = (w >= 0)? hostName.substring(w+1) : null;
        }

        /* get properties Resource record */
        Resource res = null;
        for (;;) { // single-pass loop

            /* check for full path match */
            if (!StringTools.isBlank(urlPath)) {

                /* "privatelabel.properties:track.example.com/track/Track" */
                { // local scope
                    String key = RESID_PrivateLabel_Properties_ + hostName + urlPath;
                    try {
                        res = Resource._getResource(sysAdminID, key);
                        if (res != null) {
                            break;
                        }
                    } catch (DBException dbe) {
                        Print.logException("Error loading Resource: " + sysAdminID + "/" + key, dbe);
                        // -- fall through to below
                    }
                }

                /* Wildcard #1: "privatelabel.properties:*.track.example.com/track/Track" */
                { // local scope
                    String key = RESID_PrivateLabel_Properties_ + "*." + hostName + urlPath;
                    try {
                        res = Resource._getResource(sysAdminID, key);
                        if (res != null) {
                            break;
                        }
                    } catch (DBException dbe) {
                        Print.logException("Error loading Resource: " + sysAdminID + "/" + key, dbe);
                    }
                }

                /* Wildcard #2: "privatelabel.properties:*.example.com/track/Track" */
                if (!StringTools.isBlank(wcHostName)) {
                    String key = RESID_PrivateLabel_Properties_ + "*." + wcHostName + urlPath;
                    try {
                        res = Resource._getResource(sysAdminID, key);
                        if (res != null) {
                            break;
                        }
                    } catch (DBException dbe) {
                        Print.logException("Error loading Resource: " + sysAdminID + "/" + key, dbe);
                    }
                }

            }

            /* "privatelabel.properties:track.example.com" */
            { // local scope
                String key = RESID_PrivateLabel_Properties_ + hostName;
                try {
                    res = Resource._getResource(sysAdminID, key);
                    if (res != null) {
                        break;
                    }
                } catch (DBException dbe) {
                    Print.logException("Error loading Resource: " + sysAdminID + "/" + key, dbe);
                    // -- fall through to below
                }
            }

            /* Wildcard #1: "privatelabel.properties:*.track.example.com" */
            { // local scope
                String key = RESID_PrivateLabel_Properties_ + "*." + hostName;
                try {
                    res = Resource._getResource(sysAdminID, key);
                    if (res != null) {
                        break;
                    }
                } catch (DBException dbe) {
                    Print.logException("Error loading Resource: " + sysAdminID + "/" + key, dbe);
                    // -- fall through to below
                }
            }

            /* Wildcard #2: "privatelabel.properties:*.example.com" */
            if (!StringTools.isBlank(wcHostName)) {
                String key = RESID_PrivateLabel_Properties_ + "*." + wcHostName;
                try {
                    res = Resource._getResource(sysAdminID, key);
                    if (res != null) {
                        break;
                    }
                } catch (DBException dbe) {
                    Print.logException("Error loading Resource: " + sysAdminID + "/" + key, dbe);
                }
            }

            /* not found */
            break;

        } // single-pass loop

        /* found resource? */
        if (res != null) {
            // -- found entry
            RTProperties rtProps = new RTProperties();
            String props = res.getProperties();
            if (props != null) {
                rtProps.setProperties(props, false);
            }
            byte value[] = res.getValue();
            if (value.length > 0) {
                rtProps.setProperties(StringTools.toStringValue(value), false);
            }
            return rtProps;
        } else {
            //Print.logInfo("Resource Key not found: " + sysAdminID + "," + hostName + "," + urlPath);
            return null;
        }

    }

    // ------------------------------------------------------------------------
    // ------------------------------------------------------------------------
    // ------------------------------------------------------------------------
    // Main admin entry point below
    
    private static final String ARG_ACCOUNT[]   = new String[] { "account" , "acct" };
    private static final String ARG_RESOURCE[]  = new String[] { "resource", "res"  };
    private static final String ARG_CREATE[]    = new String[] { "create"           };
    private static final String ARG_EDIT[]      = new String[] { "edit"    , "ed"   };
    private static final String ARG_DELETE[]    = new String[] { "delete"           };
    private static final String ARG_LIST[]      = new String[] { "list"             };
    private static final String ARG_SEQUENCE[]  = new String[] { "sequence", "seq"  };

    private static String _fmtResID(String acctID, String resID)
    {
        return acctID + "/" + resID;
    }

    private static void usage()
    {
        Print.logInfo("Usage:");
        Print.logInfo("  java ... " + Resource.class.getName() + " {options}");
        Print.logInfo("Common Options:");
        Print.logInfo("  -account=<id>   Acount ID which owns Resource");
        Print.logInfo("  -resource=<id>  Resource ID to create/edit");
        Print.logInfo("  -create         Create a new Resource");
        Print.logInfo("  -edit           Edit an existing (or newly created) Resource");
        Print.logInfo("  -delete         Delete specified Resource");
        Print.logInfo("  -list           List account Resources");
        System.exit(1);
    }

    public static void main(String args[])
    {
        DBConfig.cmdLineInit(args,true);  // main
        String acctID  = RTConfig.getString(ARG_ACCOUNT , "");
        String resID   = RTConfig.getString(ARG_RESOURCE, "");

        /* account-id specified? */
        if (StringTools.isBlank(acctID)) {
            Print.logError("Account-ID not specified.");
            usage();
        }

        /* get account */
        Account acct = null;
        try {
            acct = Account.getAccount(acctID); // may throw DBException
            if (acct == null) {
                Print.logError("Account-ID does not exist: " + acctID);
                usage();
            }
        } catch (DBException dbe) {
            Print.logException("Error loading Account: " + acctID, dbe);
            //dbe.printException();
            System.exit(99);
        }

        /* resource-id specified? */
        if (StringTools.isBlank(resID)) {
            //Print.logWarn("Resource-ID not specified.");
            //usage();
        }

        /* resource exists? */
        boolean resourceExists = false;
        try {
            resourceExists = Resource.exists(acctID, resID);
        } catch (DBException dbe) {
            Print.logError("Error determining if Resource exists: " + _fmtResID(acctID,resID));
            System.exit(99);
        }

        /* option count */
        int opts = 0;
        
        /* delete */
        if (RTConfig.getBoolean(ARG_DELETE, false) && !acctID.equals("") && !resID.equals("")) {
            opts++;
            if (!resourceExists) {
                Print.logWarn("Resource does not exist: " + _fmtResID(acctID,resID));
                Print.logWarn("Continuing with delete process ...");
            }
            try {
                Resource.Key strKey = new Resource.Key(acctID, resID);
                strKey.delete(true); // also delete dependencies
                Print.logInfo("Resource deleted: " + _fmtResID(acctID,resID));
                resourceExists = false;
            } catch (DBException dbe) {
                Print.logError("Error deleting Resource: " + _fmtResID(acctID,resID));
                dbe.printException();
                System.exit(99);
            }
            System.exit(0);
        }

        /* create */
        if (RTConfig.hasProperty(ARG_CREATE)) {
            String type = RTConfig.getString(ARG_CREATE, null);
            opts++;
            if (resourceExists) {
                Print.logWarn("Resource already exists: " + _fmtResID(acctID,resID));
            } else {
                try {
                    Resource.createNewResource(acct, resID, type);
                    Print.logInfo("Created Resource ["+type+"]: " + _fmtResID(acctID,resID));
                    resourceExists = true;
                } catch (DBException dbe) {
                    Print.logError("Error creating Resource  ["+type+"]: " + _fmtResID(acctID,resID));
                    dbe.printException();
                    System.exit(99);
                }
            }
        }

        /* edit */
        if (RTConfig.getBoolean(ARG_EDIT,false)) {
            opts++;
            if (!resourceExists) {
                Print.logError("Resource does not exist: " + _fmtResID(acctID,resID));
            } else {
                try {
                    Resource str = Resource.getResource(acct, resID, false, null); // may throw DBException
                    DBEdit editor = new DBEdit(str);
                    editor.edit(true); // may throw IOException
                } catch (IOException ioe) {
                    if (ioe instanceof EOFException) {
                        Print.logError("End of input");
                    } else {
                        Print.logError("IO Error");
                    }
                } catch (DBException dbe) {
                    Print.logError("Error editing Resource: " + _fmtResID(acctID,resID));
                    dbe.printException();
                }
            }
            System.exit(0);
        }

        /* list */
        if (RTConfig.hasProperty(ARG_LIST)) {
            String regex = RTConfig.getString(ARG_LIST,"").toLowerCase();
            opts++;
            try {
                OrderedSet<String> keyList = Resource.getResourcesForAccount(acctID, null);
                for (String resKey : keyList) {
                    if (StringTools.isBlank(regex)            || 
                        (resKey.indexOf(regex) >= 0)          ||
                        StringTools.regexMatches(resKey,regex)  ) {
                        Resource res = Resource._getResource(acctID, resKey);                        
                        if (res != null) {
                            String v = res.getStringValue();
                            Print.sysPrintln(resKey + "=" + v);
                        } else {
                            // should not occur
                        }
                    }
                }
                System.exit(0);
            } catch (DBException dbe) {
                Print.logException("Listing Resources", dbe);
                System.exit(99);
            }
        }

        /* next sequence */
        if (RTConfig.hasProperty(ARG_SEQUENCE)) {
            int repeatCount = RTConfig.getInt(ARG_SEQUENCE,1);
            opts++;
            try {
                for (int i = 1; i <= repeatCount; i++) {
                    long seq = Resource.getGlobalNextSequence(resID, -1L);
                    Print.sysPrintln(i + ") Next Sequence: " + seq);
                }
            } catch (DBException dbe) {
                Print.logException("Getting Next Sequence", dbe);
                System.exit(99);
            }
        }

        /* no options specified */
        if (opts == 0) {
            Print.logWarn("Missing options ...");
            usage();
        }

    }

    // ------------------------------------------------------------------------

}
