/*  Copyright (c) 2003-2004 Jeremy Boschen
    
    sprintf.js - sprintf function for Javascript.
    
    Author:     Jeremy Boschen <jboschen@earthlink.net>
    Version:    1.0.0.1
    Created:    01/15/2003

    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 2 of the License, or
    (at your option) any later version.
    
    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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
function __sprintf( szFormat ) {
    //          misc  %[        flags         ][  width  ] [grouping]      [precision ]     type                    misc
    //                 [-] [+]  [ ]    [#]  [0] [digit|*]  [,digit|*]      [.digit|*]       type
    var rgx = /([^%]*)%(-)?(\+)?(\x20)?(\#)?(0)?([\d|\*]+)?(?:\,([\d\*]+))?(?:\.([\d\*]+))?(%|b|c|d|i|o|u|x|X|e|f|s)(.*)/
    // 10 components
    
    // Match array
    var mrg = [];
    // Match count
    var ima = 0;
    // Substitution index
    var isx = 0;
    // Arguments length
    var arl = arguments.length;
    
    while ( mrg = rgx.exec( szFormat ) ) {
        var mca = mrg[1], // misc a
            fal = mrg[2], // flag : align left
            fps = mrg[3], // flag : prefix with +/-
            flb = mrg[4], // flag : blank space
            fns = mrg[5], // flag : 0x, 0X, force decimal
            flz = mrg[6], // flag : leading zeros
            wid = mrg[7], // width
            gwd = mrg[8], // grouping width
            pre = mrg[9], // precision
            typ = mrg[10],// type
            mcb = mrg[11],// misc b
            sbs = '';     // substitution
         
        ima++;
                
        // Special case for %%
        if ( '%' == typ ) {
            sbs = '%';
        } else {
            isx++;
            
            if ( isx >= arl ) {
                // Not enough arguments to satisfy the format string
                throw new Error(0, 'Not enough arguments to satisfy ' + mrg[0]);
            }
            
            // Alignment : Left == true / Right == false
            if ( '-' == fal ) {
                fal = true;
            } else {
                fal = false;
            }
            
            // Sign Prefix : Yes == true / No == false
            if ( '+' == fps ) {
                fps = true;
            } else {
                fps = false;
            }
            
            // Blank Prefix : Yes == true / No == false : Ignored if sign prefix was specified
            if ( '\x20' == flb && !fps ) {
                flb = true;
            } else {
                flb = false;
            }
            
            // Octal/Hex Prefix / Force Decimal : Yes == true / No == false
            if ( '#' == fns ) {
                fns = true;
            } else {
                fns = false;
            }
            
            // Leading 0's : Yes == true / No == false / Ignored if - is specified
            if ( '0' == flz && !fal ) {
                flz = true;
            } else {
                flz = false;
            }
            
            // Width
            if ( '*' == wid ) {
                wid = arguments[isx++];
                
                if ( isx >= arl ) {
                    // Not enough arguments to satisfy the format string
                    throw new Error(0, 'Not enough arguments to satisfy ' + mrg[0]);
                }
                // Fall through to the conversion below
            }
                        
            wid = parseInt(wid, 10);
                
            // Must be a positive integer
            if ( isNaN(wid) || 0 > wid ) {
                wid = 0;
            }
            
            // Grouping
            if ( '*' == gwd ) {
                gwd = arguments[isx++];
                
                if ( isx >= arl ) {
                    // Not enough arguments to satisfy the format string
                    throw new Error(0, 'Not enough arguments to satisfy ' + mrg[0]);
                }
                // Fall through to the conversion below
            }
            
            gwd = parseInt(gwd, 10);
                     
            // Must be a positive integer and not used with #
            if ( isNaN(gwd) || 0 > gwd || fns ) {
                gwd = 0;
            }
                            
            // Precision
            if ( '*' == pre ) {
                pre = arguments[isx++];
                
                if ( isx >= arl ) {
                    // Not enough arguments to satisfy the format string
                    throw new Error(0, 'Not enough arguments to satisfy ' + mrg[0]);
                }
                // Fall through to the conversion below                
            }
            
            pre = parseInt(pre, 10);
            
            // Must be an integer
            if ( isNaN(pre) || 0 > pre ) {
                // -1 means not set
                pre = -1;
            }
            
            var val = arguments[isx],
                tmp = 0,
                sgn = '' // sign;

            switch ( typ ) {
                // Binary : allows[align|width|precision]
                case 'b':
                    tmp = parseInt(val);
                    
                    if ( 0 > tmp ) {
                        tmp = 0xffffffff + tmp + 1;
                    }
                                        
                    sbs = tmp.toString(2);
                    
                    // Precison                    
                    sbs = __repeatChar('0', pre - sbs.length) + sbs;
                                        
                    // Group first if padding with ' '
                    if ( gwd && !flz ) {
                        sbs = __groupString(sbs, ' ', gwd);
                    }
                    
                    // Width / Alignment
                    tmp = __repeatChar(flz ? '0' : ' ', wid - sbs.length);
                    
                    if ( fal ) {
                        sbs = tmp + sbs;
                    } else 
                    if ( !flz ) {
                        sbs += tmp;
                    }
                    
                    // Group if padded with 0
                    if ( gwd && flz ) {
                        sbs = __groupString(sbs, ' ', gwd);
                    }
                    
                    break;
                // Character : allows[align:width:precision]
                case 'c':
                    sbs = String.fromCharCode(val);
                    
                    // Precision
                    sbs = __repeatChar('0', pre - sbs.length) + sbs;
                    
                    // Width / Alignment
                    tmp = __repeatChar(flz ? '0' : ' ', wid - sbs.length);
     
                    if ( fal ) {
                        sbs = tmp + sbs;
                    } else {
                        sbs += tmp;
                    }
                    break;
                // Signed Integer : allows[align|sign|width|precision]
                case 'd':
                case 'i':
                    tmp = parseInt(val);

                    sbs = tmp.toString(10);
                    
                    if ( !isNaN(tmp) ) {
                        // Precision
                        sbs = __repeatChar('0', pre - sbs.length) + sbs;
                    
                        // Group first if using ' ' padding
                        if ( gwd && (flb || !flz) ) {                            
                            sbs = __groupString(sbs, ',', gwd);
                        }
                        
                        // Sign
                        if ( 0 < tmp ) {
                            sgn = (flb ? ' ' : fps ? '+' : '');
                        }
                        
                        // Width / Alignment 
                        tmp = __repeatChar(flz ? '0' : ' ', wid - sbs.length);
                        
                        // Alignment with sign & zero padding ignored
                        if ( fal && !(flz && fps) ) {
                            sbs = tmp + sbs;
                        } else
                        if ( !(flz || fps) ) {
                            sbs += tmp;
                        }
                        
                        // Group if necessary
                        if ( gwd && flz && !flb ) {
                            sbs = __groupString(sbs, ',', gwd);
                        }
                        
                        // Sign
                        if ( sgn.length ) {
                            if ( sbs.length > wid ) {
                                sbs = sgn + sbs;
                            } else {
                                sbs = sbs.replace(/[^\,0-9]{1}(?=[0-9])/, sgn);
                            }
                        }
                    }                    
                    break;
                // Octal : allows[align|width|precision]
                case 'o':
                    tmp = parseInt(val);
                    sbs = tmp.toString(8);
                    
                    if ( !isNaN(tmp) ) {
                        // Prefix
                        if ( fns && (0 != tmp) ) {
                            sbs = '0' + sbs;
                        }
                        
                        // Precision
                        sbs = __repeatChar('0', pre - sbs.length) + sbs;
                        
                        // Group first if padding with ' '
                        if ( gwd && !flz ) {
                            sbs = __groupString(sbs, ' ', gwd);
                        }
                        
                        // Width / Alignment
                        tmp = __repeatChar(flz ? '0' : ' ', wid - sbs.length);
                        
                        if ( fal ) {
                            sbs = tmp + sbs;
                        } else {
                            sbs += tmp;
                        }
                        
                        // Group if necessary
                        if ( gwd && flz ) {
                            sbs = __groupString(sbs, ' ', gwd);
                        }
                    }
                    break;
                // Unsigned Integer : allows[align|width|precision]
                case 'u':
                    tmp = parseInt(val);
                    
                    if ( 0 > tmp ) {
                        tmp = 0xffffffff + tmp + 1;
                    }
                    
                    sbs = tmp.toString(10)
                    
                    // Precision
                    sbs = __repeatChar('0', pre - sbs.length) + sbs;
                    
                    // Group first if padding with ' '
                    if ( gwd && !flz ) {
                        sbs = __groupString(sbs, ',', gwd)
                    }
                    
                    // Width / Alignment
                    tmp = __repeatChar(flz ? '0' : ' ', wid - sbs.length);
                    
                    // Align left with zero padding ignored
                    if ( fal ) {
                        sbs = tmp + sbs;
                    } else
                    if ( !flz ) {
                        sbs += tmp;
                    }
                    
                    // Group if necessary
                    if ( gwd && flz ) {
                        sbs = __groupString(sbs, ',', gwd);
                    }
                    break;
                // Unsigned Hexadecimal Integer : allows[align|prefix|width|precision]
                case 'x':
                case 'X':
                    tmp = parseInt(val);

                    if ( 0 > tmp ) {
                        tmp = 0xffffffff + tmp + 1;
                    }
                    
                    sbs = tmp.toString(16);
                    
                    // Precision
                    sbs = __repeatChar('0', pre - sbs.length) + sbs;
                    
                    // Prefix
                    if ( fns && (0 != tmp) ) {
                        sbs = '0x' + sbs;
                    }
                    
                    // Group first if padding with ' '
                    if ( gwd && !flz ) {
                        sbs = __groupString(sbs, ' ', gwd);
                    }
                    
                    // Width / Alignment
                    tmp = __repeatChar(' ', wid - sbs.length);
                    
                    if ( fal ) {
                        sbs = tmp + sbs;
                    } else {
                        sbs += tmp;
                    }
                    
                    if ( gwd && flz ) {
                        sbs = __groupString(sbs, ' ', gwd);
                    }
                    
                    // Case
                    if ( 'x' != typ ) {
                        sbs = sbs.toUpperCase();
                    }                    
                    break;
                // Exponential Decimal : allows[align|sign|width|precision]
                case 'e':
                    tmp = parseFloat(val);
                    
                    // Precision 
                    pre = Math.min(pre, 20);
                    
                    if ( -1 == pre ) {
                        pre = 6;
                    }
                    
                    sbs = tmp.toExponential(pre);
                                        
                    // Sign
                    if ( 0 < tmp ) {                        
                        sbs = (flb ? ' ' : fps ? '+' : '') + sbs;
                    }
                    
                    // Width / Alignment
                    tmp = __repeatChar(flz ? '0' : ' ', wid - sbs.length);
                    
                    if ( fal ) {
                        sbs = tmp + sbs;
                    } else 
                    if ( !flz ) {
                        sbs += tmp;
                    }
                    
                    break;
                // Fixed Decimal : allows[align|sign|width|precision]
                case 'f':
                    tmp = parseFloat(val);
                    
                    // Precision, 1 - 20
                    pre = Math.min(pre, 20);
                    
                    if ( -1 == pre ) {
                        pre = 6;
                    }
                                        
                    sbs = tmp.toFixed(pre);
                    
                    // Sign
                    if ( tmp > 0 ) {
                        sgn = (flb ? ' ' : fps ? '+' : '');
                    }                    
                    
                    // Group first if padding with ' '                    
                    if ( gwd && !flz ) {
                        sbs = __groupString(sbs.replace(/(.*)\..*/, '$1'), ',', gwd) + sbs.replace(/.*(\..*)/, '$1');
                    }
                    
                    // Width / Alignment
                    tmp = __repeatChar(flz ? '0' : ' ', wid - sbs.length);
                    
                    if ( fal ) {
                        sbs = tmp + sbs;
                    } else 
                    if ( !flz ) {
                        sbs += tmp;
                    }
                    
                    // Group if necessary
                    if ( gwd && flz ) {
                        sbs = __groupString(sbs.replace(/(.*)\..*/, '$1'), ',', gwd) + sbs.replace(/.*(\..*)/, '$1');
                    }                    
                    break;
                case 's':
                    sbs = new String(val);
                    
                    // Precision
                    if ( pre > 0 && pre < sbs.length ) {
                        sbs = sbs.substr(1, pre);
                    } else {
                        // Reset precision
                        pre = Math.max(0, pre);

                        // Adjust width
                        if ( wid > pre ) {
                            wid = wid - pre;
                        }
                        
                        // Width / Alignment
                        tmp = __repeatChar(flz ? '0' : ' ', wid - sbs.length);
                        
                        if ( fal ) {
                            sbs = tmp + sbs;
                        } else {
                            sbs += tmp;
                        }
                    }
            }
        }        
        szFormat = mca + sbs + mcb;
	}
	
	return ( szFormat );
}

function __repeatChar( c, rg ) {
    var pst = '';
    
    while ( rg-- > 0 ) {
        pst += c;
    }
    
    return ( pst );
}

function __groupString( sz, grp, gwd ) {
    var rg  = sz.match(/./g),
        rgx = new RegExp('(.{' + gwd + '})', 'g'),
        rsl = '';
    
    rg = rg.reverse();    
    
    rsl = rg.join('');
    rsl = rsl.replace(rgx, '$1' + grp);
    
    rg = rsl.match(/./g);
    
    rg = rg.reverse();

    rgx = new RegExp('^' + grp);
    
    return ( rg.join('').replace(rgx, '') );
}