
// bad.js
// note: depends on str.js

function isWithin( viVal, viMin, viMax ) { return( (viVal >= viMin) && (viVal <= viMax) ); }

function isChrSpc( c ) { return( (c==32) ); }
function isChrAnd( c ) { return( (c==38) ); }
function isChrDsh( c ) { return( (c==45) ); }
function isChrPer( c ) { return( (c==46) ); }
function isChrAmp( c ) { return( (c==64) ); }
function isChrAsc( c ) { return( isWithin( c,  32, 126 ) ); }
function isChrNum( c ) { return( isWithin( c,  48, 57  ) ); }
function isChrUpr( c ) { return( isWithin( c,  65, 90  ) ); }
function isChrLwr( c ) { return( isWithin( c,  97, 122 ) ); }

// non-alpha chars used in names: ' ',",&,',-,.
function isChrEtc( c ) { return( (c==32) || (c==34) || (c==38) || (c==39) || (c==45) || (c==46) ); }

// non-alpha-numeric-etc chars allowed as input (ex: addresses): # ,
function isChrExp( c ) { return( (c==35) || (c=44) ); }

// non-numeric chars used in phone numbers: ' ',(,),+,-,.
function isChrPhn( c ) { return( (c==32) || (c==40) || (c==41) || (c==43) || (c==45) || (c==46) ); }

// non-alpha chars used in emails: +,-,.,@,^,_,~
function isChrEml( c ) { return( (c==43) || (c==45) || (c==46) || (c==64) || (c==94) || (c==95) || (c==126) ); }

// alpha-numeric characters (note: Nol=number or letter)
function isChrNol( c ) { return( isChrNum(c) || isChrUpr(c) || isChrLwr(c) || isChrSpc(c) ); }

// characters allowed in names
function isChrNam( c ) { return( isChrUpr(c) || isChrLwr(c) || isChrEtc(c) ); }

// characters allowed as input
function isChrInp( c ) { return( isChrNum(c) || isChrUpr(c) || isChrLwr(c) || isChrEtc(c) || isChrExp(c) ); }

function isValidInp( s )
{ 
   var l = s.length;
   for( var k = 0; k < l && isChrInp( s.charCodeAt(k) ); k++ );
   return ( k == l );
}

function isValidNol( s )
{ 
   var l = s.length;
   for( var k = 0; k < l && isChrNol( s.charCodeAt(k) ); k++ );
   return ( k == l );
}

function isValidNam( s )
{ 
   var l = s.length;
   for( var k = 0; k < l && isChrNam( s.charCodeAt(k) ); k++ );
   return ( k == l );
}

function isValidNum( s )
{ 
   var l = s.length;
   for( var k = 0; k < l && isChrNum( s.charCodeAt(k) ); k++ );
   return ( k == l );
}

function isValidDec( s )
{ 
   if( isEmptyStr(s) ) { return( true ); }
   // validate characters in string allowing number, leading "-", and one "."
   var c; var t; var p = -1;
   for( var k = 0; k < s.length; k++ )
   {
      t = s.charAt(k);
      c = t.charCodeAt(0);
           if( isChrSpc(c) ) { return( false ); }
      else if( isChrDsh(c) ) { if( k != 0 ) { return( false ); } }
      else if( isChrPer(c) ) { if( p == -1 ) { p = k; } else { return( false ); } }
      else if(!isChrNum(c) ) { return( false ); }
   }
   var f = parseFloat( s );   // note: 11abc "parses" to 11 :(
   return( !isNaN( f ) );
}

function isValidDtm( s )
{
   if( isEmptyStr(s) ) { return( true ); }
   var d = new Date( s );
   return( !isNaN( d ) );
}

function isValidTim( ts )
{
   if( isEmptyStr(ts) ) { return( true ); }
   var s = trimStr( ts );
   var l = s.length;
   var c = s.indexOf(':');
   var h, t;
   var bd = false;   // was there a time descriptor (am or pm)
   if (c==-1)
   {  // allow 4 digit military time
      if( l != 4 ) return( false );
      if ( !isValidNum( s ) ) return( false );
      h = s.substring( 0, 2 );
      t = s.substring( 2, 4 );
   }
   else
   {  // extract hours
      h = s.substring( 0, c );
      // extract minutes
      t = s.substring( c+1, l );
      var lt = t.length;
      if (lt > 2)
      {  // extract description
         var i = lt-2;
         var d = t.substring( i, lt );
         d.toLowerCase();
         bd = ( (d=='am') || (d=='pm') ); 
         if( !bd ) return( false );
         t = trimStr( t.substring( 0, i ) );
         lt = t.length;
      }
      if (lt != 2) return( false );
   }
   // validate hours
   if ( !isValidNum( h ) ) return( false );
   var nh = parseInt( h );
   // hours must be within military range
   if ( (nh < 0) || (nh > 24) ) return( false );
   // must be 1-12 hours if time description was specified
   if ( bd && ( (nh==0) || (nh>12) ) ) return( false );
   // validate minutes
   if ( !isValidNum( t ) ) return( false );
   var nm = parseInt( t ); 
   // minutes must be within 0-60
   if ( (nm < 0) || (nm > 59) ) return( false );
   // must be 0001-2400 for military time (where hour is fully specified (i.e. '1:31' is not valid)
   if ( (!bd) && (((nh==24) && (nm!=0)) || ((nh==0) && (nm==0)) || (h.length!=2)) ) return( false );
   return( true );
}

function isValidEml( s )
{
   if( isEmptyStr(s) ) { return( true ); }
   // validate characters in string pulling out @ and last .
   var c; var t; var a = -1; var p;
   for( var k = 0; k < s.length; k++ )
   {
      t = s.charAt(k);
      c = t.charCodeAt(0);
      if( isChrSpc(c) ) { return( false ); }
      if(!( isChrNol(c) || isChrEml(c) ) ) { return( false ); }
      if( isChrAmp(c) ) { if( a == -1 ) { a = k; } else { return( false ); } }
      if( isChrPer(c) ) { p = k; }
   }
   return( ( (a > 0) && (p >= (a + 2)) && (p < (s.length - 1)) ) );
}

function isValidSsn( s )
{
   if( isEmptyStr(s) ) { return( true ); }
   // validate characters in string pulling out digits
   var c; var t; var d = '';
   for( var k = 0; k < s.length; k++ )
   {
      t = s.charAt(k);
      c = t.charCodeAt(0);
      if( isChrNum(c) ) { d += t; }
      // bail if non-numeric character is not a dash or space
      else if( !( isChrDsh(c) || isChrSpc(c)) ) { return( false ); }

   }
   var l = d.length;
   return( (l==4) || (l==9) ); // allow 4 digit partial SSN to validate
}

function isValidZip( s )   // USA
{
   if( isEmptyStr(s) ) { return( true ); }
   // validate characters in string pulling out digits
   var c; var t; var d = '';
   for( var k = 0; k < s.length; k++ )
   {
      t = s.charAt(k);
      c = t.charCodeAt(0);
      if( isChrNum(c) ) { d += t; }
      else if( !(isChrDsh(c) && (k==5)) ) { return( false ); }
   }
   var l = d.length;
   return( (l==5) || (l==9) );
}

function isValidPhn( s )
{
   if( isEmptyStr(s) ) { return( true ); }

   // validate characters in string pulling out digits
   //    + allowed as first character
   //    only one pair of parens "()" allowed
   //    dash,period,comma allowed as separators
   //    (provided the separators do not occur together)

   var pl = -1;   // index of left paren
   var pr = -1;   // indes of right paren
   var ls = -2;   // index of last separator
   var nx = -1;   // number of digits in extension (if any)
   var b  =  0;   // number of leading spaces
   var l  =  0;   // number of digits in phone
   var d  = '';   // string of digits
   var c; var t;  // current ascii code and character being analyzed

   for( var k = 0; k < s.length; k++ )
   {
      t = s.charAt(k);

      if( isWhitespace(t) )
      { 
         // tally leading whitespace
         if (l==0) { b += 1; }
      }
      else
      {
         c = t.charCodeAt(0);
         switch( c )
         {
            case 43: // if +, must be first
               if (k!=b) { return( false ); }
               break;

            case 41: // ) must follow (; only once not consecutively
               if ((pr>-1)||(pl==-1)||((k-pl)==1)) { return( false ); }
               pr=k; break;

            case 40: // only 1 (
               if (pl>-1) { return( false ); }
               pl=k; break;

            default: 
               // separators must not be consecutive (nor prior to first number)
               if ((c>=44)&&(c<=46))
               {
                  if (((k-ls)==1) || (l==0)) { return( false );  }
                  ls=k; break;
               }

               // handle possible extension
               if (!((c>=48) && (c<=57)))
               {
                  // note: must have 7 digits prior to any extension !!!
                  if ((nx<0) && (l>=7))
                  {
                     if ((c==88) || (c==120)) // x?
                     {
                        nx = 0;
                        break;
                     }
                     if ((c==69) || (c==101)) // e?
                     {
                        k++;
                        var tx = s.charAt(k) + s.charAt(k+1);
                        if (tx.toLowerCase()=='xt')
                        {
                           nx = 0;
                           k += 2;
                           break;
                        }
                     }
                  }
                  // not a number or extension identifier
                  return( false );
               }

               // the rest must be numbers
               d += t;
               if (nx < 0) { l += 1; } // tally digits
               else { nx += 1; }       // tally extension
         }
      }
   }

   var nonPar = ((pl< 0)&&(pr< 0));
   var hasPar = ((pl>-1)&&(pr>-1));

   // vaidate paren pair
   if (!(nonPar||hasPar)) { return( false ); }              // not exists or paired paren

   // validate local phone number
   if( l == 7 ) return( !(hasPar) );                        // should not have any parens !!

   // validate area coded phone number
   if( l == 10 ) return(  nonPar || (hasPar && (pl==b)) );  // any paren better be first

   // validate international phone number
   return( l > 10 );
}

function validateInp( r, s, d )  { return( isValidInp(s) ? r : joinStr( r, ', ', d ) ); }
function validateNol( r, s, d )  { return( isValidNol(s) ? r : joinStr( r, ', ', d ) ); }
function validateNam( r, s, d )  { return( isValidNam(s) ? r : joinStr( r, ', ', d ) ); }
function validateNum( r, s, d )  { return( isValidNum(s) ? r : joinStr( r, ', ', d ) ); }
function validateDec( r, s, d )  { return( isValidDec(s) ? r : joinStr( r, ', ', d ) ); }
function validateDtm( r, s, d )  { return( isValidDtm(s) ? r : joinStr( r, ', ', d ) ); }
function validateTim( r, s, d )  { return( isValidTim(s) ? r : joinStr( r, ', ', d ) ); }
function validateEml( r, s, d )  { return( isValidEml(s) ? r : joinStr( r, ', ', d ) ); }
function validatePhn( r, s, d )  { return( isValidPhn(s) ? r : joinStr( r, ', ', d ) ); }
function validateZip( r, s, d )  { return( isValidZip(s) ? r : joinStr( r, ', ', d ) ); }
function validateSsn( r, s, d )  { return( isValidSsn(s) ? r : joinStr( r, ', ', d ) ); }
function requireStr( r, s, d )   { return( isValidStr(s) ? r : joinStr( r, ', ', d ) ); }

function inputValid( v, s, d )   { return( isValidStr(v) && (s.indexOf(d)==-1) ); }

function resolveStr( vsStr, vsOne, vsMny )
{
   if (!isInStr(vsStr,',')) return( vsStr + vsOne );
   return( vsMny + vsStr.toLowerCase() );
}

function invalidStr( vsReq, vsVld )
{
   var r = ( isEmptyStr( vsReq ) ? '' : resolveStr( vsReq, " is required.", "Please provide the following required values: " ) );
   var v = ( isEmptyStr( vsVld ) ? '' : resolveStr( vsVld, " is not valid.", "Please provide appropriate values for the following: " ) );
   return( joinStr( r, '\n', v ) );
}

function alertStr( vsStr )
{  
   var b = isValidStr( vsStr );
   if( b ) { alert( vsStr ); }
   return( b );
}

function alertValidation( vsReq, vsVld )
{
   return( alertStr( invalidStr( vsReq, vsVld ) ) );
}

var jsReq = '';
var jsVld = '';

function clrBadInput()  { jsReq=''; jsVld=''; }
function hasBadInput()  { return( isValidStr( jsReq ) || isValidStr( jsVld ) ); }
function addBadInput(d) { jsVld = joinStr( jsVld, ', ', d ); }
function shoBadInput(e) { return( alertStr( joinStr( invalidStr( jsReq, jsVld ), '\n', e ) ) ); }
