// Copyright (C) 2007-2012 Bristle Software, Inc.
// 
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 1, or (at your option)
// any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc.

/******************************************************************************
* com.bristle.jslib.Validate.js
*******************************************************************************
* Purpose:
*       This file contains validation routines that are reusable from any
*       JavaScript or HTML file.
* Usage:
*       - The typical scenario for using this file from an HTML file is:
*         <script language='JavaScript' src='com.bristle.jslib.Validate.js'></script>
*         Call the various functions that reside here.
* Assumptions:
*       - The file "com.bristle.jslib.Util.js" has already been loaded.
* Effects:
*       - None.  These routines determine whether values are valid, but do 
*         nothing to enforce the validity.  That job is left to the caller.
*         See also:  com.bristle.jslib.Enforce.js.
* Anticipated Changes:
* Notes:
* Implementation Notes:
* Portability Issues:
* Revision History:
*   $Log$
******************************************************************************/

// Create the "namespace" to hold the functions in this file.
com.bristle.jslib.Validate = {};

/******************************************************************************
* Return True if the specified string value is made up of one or more
* zeros; False otherwise.
******************************************************************************/
com.bristle.jslib.Validate.isAllZeros =
function(strIn)
{
    if (strIn.length == 0)
    {
        return false;
    }
    for (var i = 0; i < strIn.length; i++)
    {
        if (strIn.charAt(i) != "0")
        {
            return false;
        }
    }
    return true;
}

/******************************************************************************
* Return True if the specified string value is a positive integer or 
* empty; False otherwise.
******************************************************************************/
com.bristle.jslib.Validate.isPositiveIntegerOrEmpty =
function(strIn)
{
    for (var i = 0; i < strIn.length; i++)
    {
        if ((strIn.charAt(i) < "0") || (strIn.charAt(i) > "9"))
        {
            return false;
        }
    }
    return true;
}

/******************************************************************************
* Return True if the specified string value is a positive integer; 
* False otherwise.
******************************************************************************/
com.bristle.jslib.Validate.isPositiveInteger =
function(strIn)
{
    return (strIn.length == 0) 
           ? false 
           : com.bristle.jslib.Validate.isPositiveIntegerOrEmpty(strIn);
}

/******************************************************************************
* Return True if the specified string value is a non-zero positive 
* integer or empty;  False otherwise.
******************************************************************************/
com.bristle.jslib.Validate.isNonZeroPositiveIntegerOrEmpty =
function(strIn)
{
    return (com.bristle.jslib.Validate.isAllZeros(strIn))
           ? false 
           : com.bristle.jslib.Validate.isPositiveIntegerOrEmpty(strIn);
}

/******************************************************************************
* Return True if the specified string value is a non-zero positive 
* integer;  False otherwise.
******************************************************************************/
com.bristle.jslib.Validate.isNonZeroPositiveInteger =
function(strIn)
{
    return (com.bristle.jslib.Validate.isAllZeros(strIn))
           ? false 
           : com.bristle.jslib.Validate.isPositiveInteger(strIn);
}

/******************************************************************************
* Return True if strIn contains any of the chars in strChars; 
* False otherwise.
******************************************************************************/
com.bristle.jslib.Validate.containsAnyChar =
function(strIn, strChars)
{
    // Loop through the specified chars, searching strIn for each char.
    // Stop at the first match.
    for (var i = 0; i < strChars.length; i++)
    {
        if (strIn.indexOf(strChars.charAt(i)) >= 0)
        {
            return true;
        }
    }
    return false;
}

/******************************************************************************
* Return True if strIn contains any char other than those in strChars; 
* False otherwise.
******************************************************************************/
com.bristle.jslib.Validate.containsAnyOtherChar =
function(strIn, strChars)
{
    // Loop through strIn, searching strChars for each char.
    // Stop at the first non-match.
    for (var i = 0; i < strIn.length; i++)
    {
        if (strChars.indexOf(strIn.charAt(i)) < 0)
        {
            return true;
        }
    }
    return false;
}

/******************************************************************************
* Get the message currently shown in the specified HTML span.
*
*@param spanMessage     The SPAN to get the message from
*@return                The message currently shown in the span.
******************************************************************************/
com.bristle.jslib.Validate.getMessageInSpan =
function(spanMessage)
{
    if (spanMessage) 
    {
        return spanMessage.innerHTML;
    }
    return "";
}

/******************************************************************************
* Show specified message in specified HTML span, making the span visible
* if the message has any text, invisible otherwise, and returning true if the
* resulting span is visible, false otherwise.
*
* Notes:
* - The span can be null to cause nothing to be done.
* - The message can be null or empty string to cause the span to be cleared
*   and hidden.
* - The message can include HTML markup.
*
*@param spanMessage     The SPAN in which to show the message.
*@param strMessage      The message to show.  Can include HTML markup.
*@return                True if span finishes visible (so user should be 
*                       looking at a message); false otherwise.
******************************************************************************/
com.bristle.jslib.Validate.showMessageInSpan =
function(spanMessage, strMessage)
{
    var blnSpanSet = false;
    if (spanMessage) 
    {
        strMessage = com.bristle.jslib.Util.mapNullToEmptyString(strMessage);
        spanMessage.innerHTML = strMessage;
        blnSpanSet = (strMessage != "");
        com.bristle.jslib.Util.setVisible(spanMessage, blnSpanSet); 
    }
    return blnSpanSet;
}

/******************************************************************************
* Show specified message in the title popup of the specified HTML control,
* returning true if the title popup is set to a non-empty value; false 
* otherwise.
*
* Notes:
* - The control can be null to cause no title popup to be set.
* - The message can include HTML markup, but it will be shown as markup, not
*   rendered.
*
*@param ctl             The HTML control of which to set the title popup.
*@param strMessage      The message to show in the title popup.
*@return                True if the title popup is set to a non-empty value; 
*                       false otherwise.
******************************************************************************/
com.bristle.jslib.Validate.showMessageInTitlePopup =
function(ctl, strMessage)
{
    var blnTitleSet = false;
    if (ctl)
    {
        strMessage = com.bristle.jslib.Util.mapNullToEmptyString(strMessage);
        // Note: Suppress all errors while setting the title popup.  Not
        //       important enough to allow an error to be thrown.
        try
        {
            // Note: Don't do it for select boxes in IE.
            //       In IE6 and earlier, title popups are not supported, so it
            //       would get silently ignored.
            //       In IE 7.0.5730.11, there is a bug.  If you set the value
            //       of the title to a different value from what it already
            //       has, while the dropdown is shown, the dropdown gets 
            //       hidden as soon as the user releases the mouse, without
            //       giving the user a chance to change the value.  Forces
            //       the user to use the keyboard, which doesn't even occur
            //       to many users, so they are completely unable to change
            //       the value.
            //       We could do a more sophisticated check for IE version,
            //       but we don't know if/when this bug will be fixed, and
            //       there is currently no known version where it is already
            //       implemented, and doesn't already/still have the bug.
            //       Also, the title popup is probably just not that important
            //       compared to the major disaster of the user being unable 
            //       to change the value.
            if (navigator.appName.indexOf("Microsoft") < 0
                ||
                typeof(ctl.selectedIndex) == "undefined"
                )
            {
                ctl.title = strMessage;
                blnTitleSet = (strMessage != "");
            }
        }
        catch (exception)
        {
            // Nothing to do.
        }
    }
    return blnTitleSet;
}

/******************************************************************************
* Show specified messages, by showing them in the title popup of the specified
* HTML control and in the specified HTML span, making the span visible if the 
* message has any text, invisible otherwise, and returning true if a non-empty
* message is shown; false otherwise.
*
* Notes:
* - The control can be null to cause no title popup to be set.
* - The span can be null to cause no span to be set.
* - The messages can be null or empty string to cause the span to be cleared
*   and hidden, or the title to be cleared.
* - The message for the span can include HTML markup.
* - The message can include HTML markup, but it will be shown as markup, not
*   rendered.
*
*@param ctl                 The HTML control of which to set the title popup.
*@param strMessageForPopup  The message to show in the title popup, where HTML
*                           markup is not supported.
*@param spanMessage         The HTML span in which to show the message.
*@param strMessageForSpan   The message to show in the span where HTML markup
*                           is supported.
*@return                    True if a non-empty message is shown in either the 
*                           span or the title popup (so the user should be 
*                           looking at a message); false otherwise.  Can be 
*                           false because the strings were empty or because 
*                           the ctl and span were both null.
******************************************************************************/
com.bristle.jslib.Validate.showMessageInTitlePopupAndSpan =
function(ctl
        ,strMessageForPopup
        ,spanMessage
        ,strMessageForSpan
        )
{
    var blnPopup = com.bristle.jslib.Validate.showMessageInTitlePopup
                                        (ctl, strMessageForPopup);
    var blnSpan  = com.bristle.jslib.Validate.showMessageInSpan
                                        (spanMessage, strMessageForSpan);
    return blnPopup || blnSpan;
}

/******************************************************************************
* Show specified message, by showing it in the title popups of the specified
* HTML controls and in the specified HTML span, making the span visible if the 
* message has any text, invisible otherwise, and returning true if a non-empty
* message is shown; false otherwise.
*
* Notes:
* - The controls can be null to cause no title popup to be set.
* - The span can be null to cause no span to be set.
* - The message can be null or empty string to cause the span to be cleared
*   and hidden, and the titles to be cleared.
* - The message can include HTML markup, but in the title popups it will be 
*   shown as markup, not rendered like it is in the span.
*
*@param ctl1            The HTML control (typically an input field) of 
*                       which to set the title popup.
*@param ctl2            The HTML control (typically a label, td, or span
*                       used as a prompt near the input field) of which 
*                       to set the title popup.
*@param spanMessage     The HTML span in which to show the message.
*@param strMessage      The message to show.
*@return                True if a non-empty message is shown in either the 
*                       span or a title popup (so the user should be looking 
*                       at a message); false otherwise.  Can be false because
*                       the string was empty or because the controls and span
*                       were all null.
******************************************************************************/
com.bristle.jslib.Validate.showMessageInTwoTitlePopupsAndSpan =
function(ctl1
        ,ctl2
        ,spanMessage
        ,strMessage
        )
{
    var blnCtl1  = com.bristle.jslib.Validate.showMessageInTitlePopup
                                        (ctl1, strMessage);
    var blnCtl2  = com.bristle.jslib.Validate.showMessageInTitlePopup
                                        (ctl2, strMessage);
    var blnSpan  = com.bristle.jslib.Validate.showMessageInSpan
                                        (spanMessage, strMessage);
    return blnCtl1 || blnCtl2 || blnSpan;
}

/******************************************************************************
* Show specified message, by showing it in the title popups of the specified
* HTML controls and in the specified HTML spans, making the spans visible if 
* the message has any text, invisible otherwise, and returning true if a 
* non-empty message is shown; false otherwise.
*
* Notes:
* - The controls can be null to cause no title popup to be set.
* - The spans can be null to cause no span to be set.
* - The message can be null or empty string to cause the spans to be cleared
*   and hidden, and the titles to be cleared.
* - The message can include HTML markup, but in the title popups it will be 
*   shown as markup, not rendered like it is in the spans.
*
*@param ctl1            The HTML control (typically an input field) of 
*                       which to set the title popup.
*@param ctl2            The HTML control (typically a label, td, or span
*                       used as a prompt near the input field) of which 
*                       to set the title popup.
*@param spanMessage1    An HTML span in which to show the message.
*@param spanMessage2    Another HTML span in which to show the message.
*@param strMessage      The message to show.
*@return                True if a non-empty message is shown in either a span 
*                       or a title popup (so the user should be looking at a
*                       message); false otherwise.  Can be false because
*                       the string was empty or because the controls and spans
*                       were all null.
******************************************************************************/
com.bristle.jslib.Validate.showMessageInTwoTitlePopupsAndTwoSpans =
function(ctl1
        ,ctl2
        ,spanMessage1
        ,spanMessage2
        ,strMessage
        )
{
    var blnCtl1  = com.bristle.jslib.Validate.showMessageInTitlePopup
                                        (ctl1, strMessage);
    var blnCtl2  = com.bristle.jslib.Validate.showMessageInTitlePopup
                                        (ctl2, strMessage);
    var blnSpan1 = com.bristle.jslib.Validate.showMessageInSpan
                                        (spanMessage1, strMessage);
    var blnSpan2 = com.bristle.jslib.Validate.showMessageInSpan
                                        (spanMessage2, strMessage);
    return blnCtl1 || blnCtl2 || blnSpan1 || blnSpan2 ;
}

/******************************************************************************
* Show specified error message in the title popups of the specified HTML 
* control and label and in the specified HTML span if the value of the 
* specified control does not pass the specified validations, returning true 
* if valid, false otherwise.
* Set the span visible if the message is not empty, invisible otherwise.
* For more info, see the Java class:
*       com.bristle.javalib.ui.PropertyDisplayData.
*
* Notes:
* - The span can be null to cause no message to be displayed.
* - The messages can be null or empty strings to cause the span to be cleared
*   and hidden.
* - The message can include HTML markup, but in the title popups it will be 
*   shown as markup, not rendered like it is in the span.
*
*@param ctl                 The input field (any HTML control with a "title" 
*                           popup and a value field).
*@param lbl                 The label (any HTML control with a "title" popup)
*                           for the input field.
*@param spanMessage         The SPAN in which to show the error message.
*@param strCurrentValue     The current value of the field.
*@param strOriginalValue    The original value of the field, to decide if it
*                           has changed.
*@param strDataType         The data type of the field.  One of:
*                               com.bristle.jslib.Validate.dataType_STRING
*                               com.bristle.jslib.Validate.dataType_INTEGER
*                               com.bristle.jslib.Validate.dataType_FLOAT
*                               com.bristle.jslib.Validate.dataType_DATE_TIME
*@param strDataTypeMessage  The message to show if the field is not of the 
*                           specified data type.
*@param blnRequired         The field is required to be non-empty
*@param strRequiredMessage  The message to show if the field is empty.
*@param blnReadOnly         The field is readonly for the current user.
*@param strReadOnlyMessage  The message to show if the readonly field is changed.
*@param intMaxLength        Max char length of the field
*@param strMaxLengthMessage The message to show if the field is too long.
*@param strDisallowedChars  Chars not allowed in the field
*@param strDisallowedCharsMessage
*                           The message to show if the field contains 
*                           disallowed chars.
*@param fltMinValue         Min value for numeric data types
*@param strMinValueMessage  The message to show if the field is less than min.
*@param fltMaxValue         Max value for numeric data types
*@param strMaxValueMessage  The message to show if the field is more than max.
*@return                    True if value is valid; false otherwise.

******************************************************************************/
com.bristle.jslib.Validate.dataType_STRING    = "STRING";
com.bristle.jslib.Validate.dataType_INTEGER   = "INTEGER";
com.bristle.jslib.Validate.dataType_FLOAT     = "FLOAT";
com.bristle.jslib.Validate.dataType_DATE_TIME = "DATE_TIME";
com.bristle.jslib.Validate.validateField =
function(ctl
        ,lbl
        ,spanMessage
        ,strCurrentValue
        ,strOriginalValue
        ,strDataType
        ,strDataTypeMessage
        ,blnRequired
        ,strRequiredMessage
        ,blnReadOnly
        ,strReadOnlyMessage
        ,intMaxLength
        ,strMaxLengthMessage
        ,strDisallowedChars
        ,strDisallowedCharsMessage
        ,fltMinValue
        ,strMinValueMessage
        ,fltMaxValue
        ,strMaxValueMessage
        )
{
    //?? How to put these chars in PropertyDisplayData w/o causing syntax 
    //?? errors in the JSP page that tries to access disallowedChars as a 
    //?? bean property via a JSP/JSTL EL expression?  For now, hardcode them
    //?? here.
    strDisallowedChars += "\'\"\\";

    // Clear the message right away in case it doesn't get set below.
    com.bristle.jslib.Validate.showMessageInTwoTitlePopupsAndSpan
                        (ctl, lbl, spanMessage, "");

    // Note:  Force strCurrentValue to a String.  It might be a number or 
    //        Boolean or something in which case some of the tests below
    //        can fail because methods like indexOf() are undefined.    
    var strVal = strCurrentValue.toString();
    if (blnReadOnly && strVal != strOriginalValue.toString())
    {
        com.bristle.jslib.Validate.showMessageInTwoTitlePopupsAndSpan
                            (ctl, lbl, spanMessage, strReadOnlyMessage);
        return false;
    }
    if (strVal.length <= 0)
    {
        if (blnRequired)
        {
            com.bristle.jslib.Validate.showMessageInTwoTitlePopupsAndSpan
                            (ctl, lbl, spanMessage, strRequiredMessage);
            return false;
        }
        else
        {
            // Don't bother with any more validations if a non-required 
            // value is entirely empty.
            return true;
        }
    }
    if (strVal.length > intMaxLength)
    {
        com.bristle.jslib.Validate.showMessageInTwoTitlePopupsAndSpan
                        (ctl, lbl, spanMessage, strMaxLengthMessage);
        return false;
    }
    if (com.bristle.jslib.Validate.containsAnyChar(strVal, strDisallowedChars))
    {
        com.bristle.jslib.Validate.showMessageInTwoTitlePopupsAndSpan
                        (ctl, lbl, spanMessage, strDisallowedCharsMessage);
        return false;
    }
    if (strDataType == com.bristle.jslib.Validate.dataType_INTEGER)
    {
        // Check for invalid chars
        if (com.bristle.jslib.Validate.containsAnyOtherChar
                                                (strVal, "-0123456789"))
        {
            com.bristle.jslib.Validate.showMessageInTwoTitlePopupsAndSpan
                        (ctl, lbl, spanMessage, strDataTypeMessage);
            return false;
        }
        // Disallow minus sign after first char.
        if (strVal.indexOf("-",1) >= 0)
        {
            com.bristle.jslib.Validate.showMessageInTwoTitlePopupsAndSpan
                        (ctl, lbl, spanMessage, strDataTypeMessage);
            return false;
        }
        // Do the above checks before calling parseInt() because parseInt()
        // ignores trailing bogus chars.
        var intVal = parseInt(strVal, 10);
        if (isNaN(intVal))
        {
            com.bristle.jslib.Validate.showMessageInTwoTitlePopupsAndSpan
                        (ctl, lbl, spanMessage, strDataTypeMessage);
            return false;
        }
        if (intVal < fltMinValue)
        {
            com.bristle.jslib.Validate.showMessageInTwoTitlePopupsAndSpan
                        (ctl, lbl, spanMessage, strMinValueMessage);
            return false;
        }
        if (intVal > fltMaxValue)
        {
            com.bristle.jslib.Validate.showMessageInTwoTitlePopupsAndSpan
                        (ctl, lbl, spanMessage, strMaxValueMessage);
            return false;
        }
    }
    else if (strDataType == com.bristle.jslib.Validate.dataType_FLOAT)
    {
        // Check for invalid chars
        if (com.bristle.jslib.Validate.containsAnyOtherChar
                                                (strVal, "-.0123456789"))
        {
            com.bristle.jslib.Validate.showMessageInTwoTitlePopupsAndSpan
                        (ctl, lbl, spanMessage, strDataTypeMessage);
            return false;
        }
        // Disallow minus sign after first char.
        if (strVal.indexOf("-",1) >= 0)
        {
            com.bristle.jslib.Validate.showMessageInTwoTitlePopupsAndSpan
                        (ctl, lbl, spanMessage, strDataTypeMessage);
            return false;
        }
        // Disallow multiple decimal points
        if (strVal.indexOf(".") != strVal.lastIndexOf("."))
        {
            com.bristle.jslib.Validate.showMessageInTwoTitlePopupsAndSpan
                        (ctl, lbl, spanMessage, strDataTypeMessage);
            return false;
        }
        // Do the above checks before calling parseFloat() because parseFloat()
        // ignores trailing bogus chars.
        var fltVal = parseFloat(strVal);
        if (isNaN(fltVal))
        {
            com.bristle.jslib.Validate.showMessageInTwoTitlePopupsAndSpan
                        (ctl, lbl, spanMessage, strDataTypeMessage);
            return false;
        }
        if (fltVal < fltMinValue)
        {
            com.bristle.jslib.Validate.showMessageInTwoTitlePopupsAndSpan
                        (ctl, lbl, spanMessage, strMinValueMessage);
            return false;
        }
        if (fltVal > fltMaxValue)
        {
            com.bristle.jslib.Validate.showMessageInTwoTitlePopupsAndSpan
                        (ctl, lbl, spanMessage, strMaxValueMessage);
            return false;
        }
    }
    else if (strDataType == com.bristle.jslib.Validate.dataType_DATE_TIME)
    {
        // Enforce format:  YYYY-MM-DD HH:mm
        // (column numbers) 0123456789012345
        if (   com.bristle.jslib.Validate.containsAnyOtherChar
                                        (strVal, "0123456789- :")
            || strVal.length != 16
            || strVal.charAt(4)  != "-"
            || strVal.charAt(7)  != "-"
            || strVal.charAt(10) != " "
            || strVal.charAt(13) != ":"
            || com.bristle.jslib.Validate.containsAnyOtherChar
                                        (strVal.slice(0,4), "0123456789")
            || com.bristle.jslib.Validate.containsAnyOtherChar
                                        (strVal.slice(5,7), "0123456789")
            || strVal.slice(5,7) < 1
            || strVal.slice(5,7) > 12
            || com.bristle.jslib.Validate.containsAnyOtherChar
                                        (strVal.slice(8,10), "0123456789")
            || strVal.slice(8,10) < 1
            || strVal.slice(8,10) > 31
            || com.bristle.jslib.Validate.containsAnyOtherChar
                                        (strVal.slice(11,13), "0123456789")
            || strVal.slice(11,13) < 0
            || strVal.slice(11,13) > 23
            || com.bristle.jslib.Validate.containsAnyOtherChar
                                        (strVal.slice(14,16), "0123456789")
            || strVal.slice(14,16) < 0
            || strVal.slice(14,16) > 59
           )
        {
            com.bristle.jslib.Validate.showMessageInTwoTitlePopupsAndSpan
                        (ctl, lbl, spanMessage, strDataTypeMessage);
            return false;
        }
    }
    return true;
}

