/* Contains post and author formatting functions */
/* TODO: autolink, working url and img tags, possibly a bbcode parser */

/* Hash of smiley codes */
var smileys = $H({
    '&gt;:(' : 'angry',
    ':&gt;'  : 'cheesy',
    '8)' 	 : 'cool',
    ';('     : 'cry',
    ':$'     : 'embarassed',
    '&gt;:D' : 'grin',
    ':-)'    : 'smiley',
    ':-('    : 'sad',
    ':-/'    : 'undecided',
    ':/'     : 'undecided',
    '8-('    : 'rolleyes',
    '8-)'    : 'cool',
    ':-P'    : 'tongue',
    ';-)'    : 'wink',
    ':-O'    : 'shocked',
    ':-X'    : 'lipsrsealed',
    ':D'     : 'laugh',
    ':X'     : 'lipsrsealed',
    '8('     : 'rolleyes',
    ':('     : 'sad',
    ':O'     : 'shocked',
    ':)'     : 'smiley',
    ':P'     : 'tongue',
    ';)'     : 'wink',
    ':S'     : 'undecided'
});

var regex_smileys = {};
smileys.keys().each(function(s) {
    regex_smileys[s] = new RegExp('(\\\s|^)(' + s.replace(/([^A-Z0-9\-:;&])/ig, '\\$1') + ')(\\\s|$)', "gim");
});

/* Extension -> upload icon */
var ext_icon = $H({ 
    'audio':        $w('wav ogg mp3 mp2 mpc mp4 aac wma au ra m4a flac mid midi'),
    'document':     $w('doc rtf odf xml docx'), 
    'application':  $w('exe chm xpi msi hlp deb fla'),
    'html':         $w(''),
    'image':        $w('bmp gif jpg jpeg png mng psd tga tif tiff svg'),
    'package':      $w('zip rar 7z gz bz2 ace pk3 pk4 torrent cbr cbz'),
    'pdf':          $w('pdf ps'),
    'presentation': $w('ppt'),
    'script':       $w(''),
    'spreadsheet':  $w('xls mdb'),
    'template':     $w(''),
    'text':         $w('txt c cpp'),
    'video':        $w('avi mpg mpeg ogm wmv rm mov ram flv xvid divx swf')
});

/* Formats message body, including smileys, formatting codes, nl2br */
function formatMessageBody(msg) {
    // The [code] tag looks best if the following text is on the same line, e.g.:
    //     [code]blah[/code]this is good
    // The [quote] tag prefers a newline, e.g.:
    //     [quote]I'm an idiot[/quote]
    //     Yes, you are.
    // These regexes are designed to normalise the text into this form.
    msg = msg.replace(/\[\/quote\]([^\r\n])/gim, "[/quote]\n$1");
    msg = msg.replace(/\[\/code\]\s*/gim, "[/code]");
    // These regexes are designed to auto-convert Google Video/YouTube links into tags.
    msg = msg.replace(/https?:\/\/(?:[a-z]+\.)?youtube\.com\/watch\?v=([A-Z0-9_\-]+)/ig, '[tube]$1[/tube]');
    msg = msg.replace(/https?:\/\/video\.google\.com\/videoplay\?docid=(-?[0-9]+)/ig, '[gvid]$1[/gvid]');
    msg = doYouTag(msg);
    if(no_images) {
        msg = msg.replace(/\[img\]/gi,   '[url]');
        msg = msg.replace(/\[\/img\]/gi, '[/url] (image not displayed by default)');  
    }
    // Handles BBCode, smileys (where appropriate), auto-links, auto-images
    msg = BBParse(msg);
    return msg;
}

/* Renders a file upload message from an informational hash */
function formatFileUpload(file) {
    var html = '';
    // Special case: images are rendered inline as if remotely linked
    if(!no_images && (file.type.match(/^image\/(jpe?g|gif|png)$/i) || file.extn.match(/^(jpe?g|gif|png)$/i))) {
        html += '<img src="uploads/' + file.name.escapeHTML() + '" onLoad="scrollIfNeeded()"';
        html += ' alt="' + file.name.escapeHTML() + ' (' + humanFileSize(file.size) + ')" />';
    }
    else {
        html = '<a target="_blank" href="uploads/' + file.name.escapeHTML() + '">';
        html += '<img src="upload_icons/' + findUploadIcon(file.extn, file.type) + '.png"';
        html += ' width="22" height="22" align="absmiddle" /></a> ';
        html += '<a target="_blank" href="uploads/' + file.name.escapeHTML() + '">';
        html += file.name.escapeHTML() + '</a> <span class="uploadFileSize">(';
        html += humanFileSize(file.size) + ')</span>';
    }
    return html;
}

/* Parses smileys inside a given block of text */
function formatSmileys(m) {
    // Sort smileys by length descending, so we replace the longer ones first
    smileyKeys = $A(smileys.keys().sortBy(function(x) { return -x.length; }));
    smileyKeys.each(function(smiley) {
        m = m.replace(
            regex_smileys[smiley],
            '$1<img src="smileys/' + smileys[smiley] + '.gif" width="15" height="15" />$3'
        );
    });
    // Also regex your own name! BONUS! (We do this here so we don't mess with links or images)
    if(user_info) {
        m = m.replace(new RegExp('([^a-z]|^)(' + user_info.username + ')([^a-z]|$)', 'ig'), '$1<span style="font-weight: bold; color: #222277;">$2</span>$3');
    }
    return m;
}

/* Format a posted date */
function formatMessageStamp(stamp) {
    return format_time(
        stamp,
        "%b %d <span class='stampTime'>%H:%M</span>"
    );
}

/* Formats the room topic */
function formatTopic(topic) {
    topic = topic.escapeHTML();
    topic = doYouTag(topic);
    topic = formatSmileys(topic);
    topic = topic.replace(/(http:\/\/)(.*?)(\s|[,.;:]($|\n|\s)|$|\n)/gim, '<a target="_blank" href="$1$2" onClick="ignore_topic_link_click = 1">$1$2</a>$3');
    return topic;
}

/* Provides a deterministic gradient for each unique username */
function getGradientClassName(n) {
    hash  = hex_sha1(n);
    h     = (gradientText.h2d(hash.slice(0,2)) * (360/255)) - 1;
    s     = 40 + gradientText.h2d(hash.slice(2,4)) * (40/255);
    l     = 40 + gradientText.h2d(hash.slice(4,6)) * (50/255);
    c1    = { hue: h, saturation: s, value: l };
    ht    = -10 +  gradientText.h2d(hash.slice(6,8)) * (20/255);
    if(ht < 0) ht = 0;
    if(ht > 359) ht = 359;
    c2 = { hue: h + ht, saturation: s, value: l - 20 };
    return ('gradient_' + hsv2rgb(c1) + '_' + hsv2rgb(c2));
}

/* Returns border colour for each unique username */
function getBorderColour(n) {
    hash = hex_sha1(n);
    h    = (gradientText.h2d(hash.slice(0,2)) * (360/255)) - 1;
    c    = { hue: h, saturation: 20, value: 80 };
    return '#' + hsv2rgb(c);
}

/* Returns single colour for nickname */
function getNameColour(n) {
    hash = hex_sha1(n);
    h    = (gradientText.h2d(hash.slice(0,2)) * (360/255)) - 1;
    c    = { hue: h, saturation: 40, value: 50 };
    return '#' + hsv2rgb(c);
}

/* Converts HSV colours to RGB */
function hsv2rgb(hsv) {
	var rgb=new Object();
	if (hsv.saturation==0) {
		rgb.r=rgb.g=rgb.b=Math.round(hsv.value*2.55);
	} else {
		hsv.hue/=60;
		hsv.saturation/=100;
		hsv.value/=100;
		i=Math.floor(hsv.hue);
		f=hsv.hue-i;
		p=hsv.value*(1-hsv.saturation);
		q=hsv.value*(1-hsv.saturation*f);
		t=hsv.value*(1-hsv.saturation*(1-f));
		switch(i) {
		case 0: rgb.r=hsv.value; rgb.g=t; rgb.b=p; break;
		case 1: rgb.r=q; rgb.g=hsv.value; rgb.b=p; break;
		case 2: rgb.r=p; rgb.g=hsv.value; rgb.b=t; break;
		case 3: rgb.r=p; rgb.g=q; rgb.b=hsv.value; break;
		case 4: rgb.r=t; rgb.g=p; rgb.b=hsv.value; break;
		default: rgb.r=hsv.value; rgb.g=p; rgb.b=q;
		}
		rgb.r=Math.round(rgb.r*255);
		rgb.g=Math.round(rgb.g*255);
		rgb.b=Math.round(rgb.b*255);
        
	}
	return gradientText.d2h(rgb.r) + gradientText.d2h(rgb.g) + gradientText.d2h(rgb.b);
}

/* Takes file extension and mime-type and returns a suitable upload icon */
function findUploadIcon(ext, type) {
    // First iterate through file extensions
    var icon = '';
    if(ext) {
        ext_icon.each(function(pair) {
            if(pair.value.indexOf(ext) != -1) {
                icon = pair.key;
            }
        });
    }
    
    // If we didn't find an icon, or there was no extension, look at mime type
    if((!ext || !icon) && type) {
        var mime_type = type.match(/^[^\/]+/).first();
        if(mime_type && ext_icon[mime_type]) {
            icon = mime_type;
        }
    }
    
    if(!icon) icon = 'attachment'; // Fallback to paperclip
    
    return icon;
}

/* Converts a filesize (in bytes) into human-readable format */
function humanFileSize(bytes) {
    if(bytes / 1048576 > 1) {
        return Math.round(bytes / 1048576, 1) + ' MB';
    }
    else if(bytes / 1024 > 1) {
        return Math.round(bytes / 1024, 1) + ' KB';
    }
    else {
        return Math.round(bytes) + ' bytes';
    }
}

/* This is where the magic is */
function doYouTag(text) {
    return text.replace(/\[you\]/gi, user_info ? user_info.username : ($F('username') ? $F('username') : 'Guest'));
}

/* Contains custom JS date functions */
/* Thanks to http://redhanded.hobix.com/inspect/showingPerfectTime.html */

var strftime_funks = {
  zeropad: function( n ){ return n>9 ? n : '0'+n; },
  a: function(t) { return ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'][t.getDay()] },
  A: function(t) { return ['Sunday','Monday','Tuedsay','Wednesday','Thursday','Friday','Saturday'][t.getDay()] },
  b: function(t) { return ['Jan','Feb','Mar','Apr','May','Jun', 'Jul','Aug','Sep','Oct','Nov','Dec'][t.getMonth()] },
  B: function(t) { return ['January','February','March','April','May','June', 'July','August',
      'September','October','November','December'][t.getMonth()] },
  c: function(t) { return t.toString() },
  d: function(t) { return this.zeropad(t.getDate()) },
  D: function(t) { return t.getDate() },
  h: function(t) { return t.getHours() },
  H: function(t) { return this.zeropad(t.getHours()) },
  i: function(t) { return (t.getHours() + 12) % 12 },
  I: function(t) { return this.zeropad((t.getHours() + 12) % 12) },
  m: function(t) { return this.zeropad(t.getMonth()+1) }, // month-1
  M: function(t) { return this.zeropad(t.getMinutes()) },
  p: function(t) { return this.H(t) < 12 ? 'AM' : 'PM'; },
  S: function(t) { return this.zeropad(t.getSeconds()) },
  w: function(t) { return t.getDay() }, // 0..6 == sun..sat
  y: function(t) { return this.zeropad(this.Y(t) % 100); },
  Y: function(t) { return t.getFullYear() },
  '%': function(t) { return '%' }
};

/* Converts a date to a string specified in strftime format */
Date.prototype.strftime = function (fmt) {
    var t = this;
    for (var s in strftime_funks) {
        if (s.length == 1 )
            fmt = fmt.replace('%' + s, strftime_funks[s](t));
    }
    return fmt;
};

/* Converts a unix timestamp to the user's local time */
function format_time(unixt, fmt) {
    if(!fmt) fmt = "%d %b %Y at %I:%M:%S %p";
    return new Date(unixt * 1000).strftime(fmt);
}

/* Short format e.g. August 17, 2007 */
function format_time_short(unixt) {
    return format_time(unixt, '%B %D, %Y');
}

/* Star correction library */
/* Enables the user to post two adjacent posts, like so:
      I like eating cats
      *cobs
     And the system will automatically perform the star correction and combine the two into one post:
      I like eating cobs
*/
function performStarCorrection(post_id, correction) {
    correction = correction.substring(1); // Trim off leading star
    var content = current_posts_inc_merged[post_id].message;
    content = content.replace(/\[\/?new\]/ig, ''); // strip old highlighting
    
    // Check each word in the target post against our correction
    var likely_word;
    var likely_score = -1;
    content.split(/\s+/).each(function(word) {
        var score = levenshteinenator(word, correction);
        if(likely_score == -1 || score < likely_score) {
            likely_score = score;
            likely_word  = word;
        }
    });
    // Perform the correction if a typo is sufficiently close to the correction
    if(likely_score != -1 && likely_score <= 5) {
        updatePost(post_id, content, likely_word, correction);
    }
}

/* Allow the direct replacement of one phrase with another */
function performStarReplacement(post_id, correction) {
    var phrases = correction.match(/^\*(.+?)->(.*)$/);
    var content = current_posts_inc_merged[post_id].message;
    content = content.replace(/\[\/?new\]/ig, ''); // strip old highlighting
    if(phrases[1]) {
        updatePost(post_id, content, phrases[1], phrases[2]);
    }
}

/* Update a chat post - should probably go in message.js later */
function updatePost(id, content, search, replace) {
    if(!replace.match(/^\s*$/)) replace = '[new]' + replace + '[/new]';
    content = content.replace(search, replace, 'i');
    // Only perform the update if it won't leave the post empty
    if(!content.match(/^\s*$/)) {
        current_posts_inc_merged[id].message = content;
        $('innerpostcontent' + id).innerHTML = renderPost_chat(current_posts_inc_merged[id]);
    }
}

/*
Copyright (c) Andrew Hedges 2006. All Rights reserved.
http://andrew.hedges.name/
*/

// calculate the Levenshtein distance between a and b, fob = form object, passed to the function
levenshteinenator = function(a, b) {
	var cost;
	
	// get values
	var m = a.length;
	var n = b.length;
	
	// make sure a.length >= b.length to use O(min(n,m)) space
	if (m < n) {
		var c=a;a=b;b=c;
		var o=m;m=n;n=o;
	}
	
	var r = new Array();
	r[0] = new Array();
	for (var c = 0; c < n+1; c++) {
		r[0][c] = c;
	}
	
	for (var i = 1; i < m+1; i++) {
		r[i] = new Array();
		r[i][0] = i;
		for (var j = 1; j < n+1; j++) {
			cost = (a.charAt(i-1) == b.charAt(j-1))? 0: 1;
			r[i][j] = minimator(r[i-1][j]+1,r[i][j-1]+1,r[i-1][j-1]+cost);
		}
	}
	
	return r[m][n];
}

// return the smallest of the three values passed in
minimator = function(x,y,z) {
	if (x < y && x < z) return x;
	if (y < x && y < z) return y;
	return z;
}