/**
 * EventEditor.js -- a fancy javascript Wikevent Event editor
 * Copyright 2006 Mark Jaroski <mark@geekhive.net>
 *
 *  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.
 *
 * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * @author Mark Jaroski <mark@geekhive.net>
 * @package MediaWiki
 * @subpackage Extensions
 */

var currentEditor = null;
var savingEditor = null;

// Number.toPadded();
//
// This new method for number works a little like Number.toFixed() but on
// the other side of the decimal point, padding integers less than 100 out
// to two decimal places if necessary.  In practice it will never get an
// int greater than 31.
Number.prototype.toPadded = function() {
    if ( this < 10 ) {
        return "0" + this.toFixed( 0 );
    } else {
        return this.toFixed( 0 ); 
    }
}

// new methods for Date
Date.prototype.toDateString = function() {
    var year = this.getFullYear();
    var month = this.getMonth();
    var day = this.getDate();
    return year + "-" + month.toPadded() + "-" + day.toPadded();
}

Date.prototype.toTimeString = function() {
    var hours = this.getHours();
    var minutes = this.getMinutes();
    return hours.toPadded() + ":" + minutes.toPadded();
}


// Element.getClassName();
// Element.setClassName();
// 
// We're going to abstract away the className problem with custom
// accessors, but first IE requires us to build our own DOM Element
//
// FIXME: there's more work to do to actually implement this for IE, but in
// the meantime at least this will let the script will load without
// completely breaking
if ( !window.Element ) {
    Element = function() {};
}

// These accessor methods paper over the differences between IE's odd DOM
// implementation and the more standard ones.
Element.prototype.getClassName = function() {
    className = this.getAttribute("class");
    if (!className) {
         className = this.getAttribute("className");
    }
    return className;
}

Element.prototype.setClassName = function( name ) {
    this.setAttribute("class", name);
    this.setAttribute("className", name);
    return;
}

// Class TimeSpan
// Constructor
function TimeSpan( n ) {
    var value;              // in milliseconds
    this.value = Number( n );
}

new TimeSpan();

TimeSpan.prototype.toNumber = function() {
    return this.value;
}

TimeSpan.prototype.getMilliseconds = function() {
    return this.value % 1000;
}

TimeSpan.prototype.getSeconds = function() {
    if ( ! this.value ) return 0;
    seconds = Math.floor( this.value / 1000 );
    seconds = seconds % 60;
    return seconds.toFixed();
}

TimeSpan.prototype.getMinutes = function() {
    if ( ! this.value ) return 0;
    minutes = Math.floor( this.value / ( 60 * 1000 ) );
    minutes = minutes % 60;
    return minutes.toFixed();
}

TimeSpan.prototype.getHours = function() {
    if ( ! this.value ) return 0;
    hours = Math.floor( this.value / ( 60 * 60 * 1000 ) );
    hours = hours % 24;
    return hours.toFixed();
}

TimeSpan.prototype.getDays = function() {
    if ( ! this.value ) return 0;
    days = Math.floor( this.value / ( 24 * 60 * 60 * 1000 ) );
    return days.toFixed();
}

TimeSpan.prototype.toString = function() {
    var ret = '';
    var days = this.getDays();
    if ( days != '0' ) {
        ret = days + 'd';
    }
    var hours = Number( this.getHours() );
    ret += hours.toPadded() + 'h';
    var minutes = Number( this.getMinutes() );
    ret += minutes.toPadded();
    return( ret );
}

TimeSpan.prototype.add = function( datespan ) {
    this.value = this.value + datespan.value;
}


// Class Event
// Constructor
function EventEditor( id ) {

    // internal attributes
    var id;
    var formid;
    var title;      // the Title of the target Article
    var from_page;  // the page a new event is being created *from*
    var md5sum;
    var complete;   // bool: can this event be saved?
    var lock;       // for locking the editor during a save

    // HTML elements
    var vevent;      // the rendered Event
    var form;        // the editor form
    var feedback;    // replaces the form during save
    var container;   // a wrapping div which lets us call replaceChild()

    // event attributes
    var venue;
    var locality;
    var where;              // free text version of locality/venue
    var name;
    var dtstart;
    var dtend;
    var duration;
    var image;
    var price;
    var tickets;
    var language;
    var restrictions;
    var description;

    this.id = id;
    this.lock = false;
    this.complete = false;

    // build a div to provide save feedback to users
    this.feedback = document.createElement( 'div' );
    this.feedback.setClassName( 'saving' );
    this.feedback.appendChild( document.createTextNode( 'saving...' ) );
}

new EventEditor();

// accessors
EventEditor.prototype.set_dtstart = function( s ) {
    if ( ! s ) return;
    this.dtstart = this.parseDate( s );
    if ( this.dtend && ! this.duration ) {
        var dur = new TimeSpan( this.dtend - this.dtstart );
        this.duration = dur.toString();
    }
    return;
}

EventEditor.prototype.set_dtend = function( s ) {
    this.dtend = this.parseDate( s );
    if ( this.dtstart && ! this.duration ) {
        var dur = new TimeSpan( this.dtend - this.dtstart );
        this.duration = dur.toString();
    }
    return;
}

EventEditor.prototype.set_duration = function( s ) {
    this.duration = s;
    // TODO fix this
//    var re = /(\d{1,2})[h :]?(\d{2})/;
//    var m = s.match( re );
//    var hrs = '';
//    var mns = '';
//    if ( m[1] ) hrs = Number( m[1] );
//    if ( m[2] ) mns = Number( m[2] );
//    var span = new TimeSpan( hrs + mns );
//    this.dtend = new Date( this.dtstart + span.toNumber() );
}

// psudeo accessors
EventEditor.prototype.getStartDate = function() {
    return this.dtstart.toDateString();
}

EventEditor.prototype.getEndDate = function() {
    return this.dtend.toDateString();
}

EventEditor.prototype.getStartTime = function() {
    return this.dtstart.toTimeString();
}

EventEditor.prototype.getEndTime = function() {
    return this.dtend.toTimeString();
}

EventEditor.prototype.getDuration = function() {
    var duration = new TimeSpan( Number( this.dtend - this.dtstart ) );
    return duration;
}

EventEditor.prototype.processFormNode = function( node ) {
    var name = node.getAttribute( "name" );
    var val  = node.value;
    switch( name ) {
        case "summary"      : this.name = val; break;
        case "venue"        : this.venue = val; break;
        case "locality"     : this.locality = val; break;
        case "image"        : this.image = val; break;
        case "price"        : this.price = val; break;
        case "duration"     : this.set_duration( val ); break;
        case "tickets"      : this.tickets = val; break;
        case "language"     : this.language = val; break;
        case "restrictions" : this.restrictions = val; break;
        case "description"  : this.description = val; break;
        case "starts"       : this.set_dtstart( val ); break;
    }
    return;
}

EventEditor.prototype.parseDate = function( s ) {
    if ( s == null ) return null;
    var datePattern = /(\d{4})-(\d{2})-(\d{2})[T ](\d{2}):(\d{2})/;
    m = s.match( datePattern );
    return new Date( m[1], m[2], m[3], m[4], m[5] );
}

EventEditor.prototype.toString = function() {
    var s = "<event \n";
    s = s + "   id=\"" + this.id + "\"\n";
    s = s + "   name=\"" + this.name + "\"\n";
    if ( this.venue != undefined )
            s = s + "   venue=\"" + this.venue + "\"\n";
    if ( this.locality != undefined )
            s = s + "   locality=\"" + this.locality + "\"\n";
    if ( this.dtstart != undefined )
            s = s + "   date=\"" + this.getStartDate() + "\"\n";
    if ( this.dtstart != undefined )
            s = s + "   time=\"" + this.getStartTime() + "\"\n";
    if ( this.duration != undefined ) 
            s = s + "   duration=\"" + this.duration + "\"\n";
    if ( this.image != undefined )
            s = s + "   image=\"" + this.image + "\"\n";
    if ( this.price != undefined )
            s = s + "   price=\"" + this.price + "\"\n";
    if ( this.tickets != undefined && this.tickets != 'http://' )
            s = s + "   tickets=\"" + this.tickets + "\"\n";
    if ( this.language != undefined )
            s = s + "   language=\"" + this.language + "\"\n";
    if ( this.restrictions != undefined )
            s = s + "   restrictions=\"" + this.restrictions + "\"\n";
    s = s + ">" + this.description + "</event>";
    return s;
}

EventEditor.prototype.toForm = function() {
    var form = document.createElement('form');
    form.setClassName( 'eventeditor' );
    // summary
    form.appendChild( this.input( 'summary', this.name, 38 ) );
    form.appendChild( this.br( true ) );
    // locality/venue
    form.appendChild( this.input( 'locality', this.locality, 15 ) );
    form.appendChild( this.input( 'venue', this.venue, 20 ) );
    form.appendChild( this.br( true ) );
    // dates
    form.appendChild( this.calendar( 'starts', this.dtstart ) );
    form.appendChild( this.durationSelect( 'duration' ) );
    form.appendChild( this.br( true ) );
    // extras
    form.appendChild( this.input( 'price', this.price ) );
    form.appendChild( this.input( 'restrictions', this.restrictions ) );
    form.appendChild( this.input( 'language', this.language ) );
    form.appendChild( this.input( 'tickets', this.tickets || 'http://' ) );
    form.appendChild( this.input( 'image', this.image ) );
    // description
    var textarea = this.textArea( 'description', this.description );
    form.appendChild( this.br( true ) );
    form.appendChild(textarea);
    // controls
    form.appendChild( this.br( true ) );
    form.appendChild(this.hidden('oldid', this.id));
    form.appendChild(this.a('save', "currentEditor.save()"));
    form.appendChild(document.createTextNode(' '));
    form.appendChild(this.a('cancel', "currentEditor.restore()"));
    // put it all together
    this.form = document.createElement( 'div' );
    this.form.setClassName( 'eventeditor' );
    this.form.appendChild( form );
    this.formid = this.id + '_form';
    this.form.setAttribute('id', this.formid);
    return this.form;
}

EventEditor.prototype.br = function( clear ) {
    br = document.createElement('br');
    if ( clear == true ) {
        br.setAttribute('clear', 'all');
    }
    return br;
}

EventEditor.prototype.a = function(text, onclick) {
    var a = document.createElement('a');
    a.setAttribute('href', 'javascript:' + onclick);
    a.setAttribute('title', text);
    a.setClassName('form-edit-button');
    a.appendChild(document.createTextNode(text));
    return a;
}
 
EventEditor.prototype.input = function( name, value, size ) {
    if ( ! size ) size = 5;
    var div = document.createElement('div');
    div.setClassName('form-edit-input');
    // label
    var label = document.createElement('label');
    label.appendChild(document.createTextNode(name));
    label.setAttribute('for', name);
    div.appendChild(label);
    div.appendChild( this.br( true ) );
    // input
    var input = document.createElement('input');
    input.setAttribute('type', 'text');
    input.setAttribute('name', name);
    input.setAttribute('id', name);
    input.setAttribute('value', (value == null) ? '' : value);
    input.setAttribute('size', (value == null) ? size : value.length + 2);
    input.setClassName( name );
    input.addEventListener( 'change', this, true );
    div.appendChild(input);
    return div;
}

EventEditor.prototype.durationSelect = function( name ) {
    var div = document.createElement('div');
    div.setClassName('form-edit-input');
    // label
    var label = document.createElement('label');
    label.appendChild(document.createTextNode(name));
    label.setAttribute('for', name);
    div.appendChild(label);
    div.appendChild( this.br( true ) );
    // input
    var select = document.createElement('select');
    select.setAttribute('type', 'text');
    select.setAttribute('name', name);
    select.setAttribute('id', name);
    select.setAttribute('value', (value == null) ? '' : value);
    select.setClassName( name );
    // create current
    var value = this.getDuration();
    var current = document.createElement('option');
    current.setAttribute('value', value );
    current.appendChild( document.createTextNode( value ) );
    // and the rest
    var time = new TimeSpan(0);
    var qtrh = new TimeSpan( 1000 * 60 * 15 );  // 15 minutes in ms
    select.appendChild( current );
    while ( time.toString() <= '12h00' ) {
        time.add( qtrh );
        var opt = document.createElement( 'option' );
        opt.setAttribute('value', time.toString() );
        opt.appendChild( document.createTextNode( time.toString() ) );
        select.appendChild( opt );
    }
    select.addEventListener( 'change', this, true );
    div.appendChild( select );
    return div;
}

EventEditor.prototype.calendar = function( name, dateval ) {
    // check for date object
    if ( dateval && ( dateval.toDateString == null ) ) 
            throw "Not a date object: " + dateval;
    var div = document.createElement('div');
    div.setClassName('form-edit-input');
    // label
    var label = document.createElement('label');
    label.appendChild(document.createTextNode(name));
    label.setAttribute('for', name);
    div.appendChild(label);
    div.appendChild( this.br( true ) );
    // value
    var value = null;
    if ( dateval )
            value = dateval.toDateString() + ' ' + dateval.toTimeString();
    // input
    var input = document.createElement('input');
    input.setAttribute('type', 'text');
    input.setAttribute('name', name);
    input.setAttribute('id', name);
    input.setAttribute('value', (value == null) ? '' : value);
    input.setAttribute('size', 15 );
    input.setClassName( name );
    input.addEventListener( 'change', this, true );
    div.appendChild(input);
    // calendar
    Calendar.setup({
        inputField     :    input,
        button         :    input,
        eventName      :    "focus",
        align          :    "right",
        ifFormat       :    "%Y-%m-%d %H:%M",
        showsTime      :    true,
        timeFormat     :    "24"
    });
    return div;
}

EventEditor.prototype.textArea = function(name, value) {
    var size = (value == null) ? 0 : value.length;
    var div = document.createElement('div');
    div.setClassName('form-edit-input');
    var label = document.createElement('label');
    label.appendChild(document.createTextNode(name));
    label.setAttribute('for', name);
    div.appendChild(label);
    div.appendChild(document.createElement('br'));
    var textarea = document.createElement('textarea');
    textarea.setAttribute('name', name);
    textarea.setAttribute('cols', 80);
    textarea.setAttribute('rows', 6 );
    textarea.appendChild(document.createTextNode((value == null) ? '' : value));
    textarea.addEventListener( 'change', this, true );
    div.appendChild(textarea);
    return div;
}

EventEditor.prototype.hidden = function(name, value) {
    var input = document.createElement('input');
    input.setAttribute('type', 'hidden');
    input.setAttribute('name', name);
    input.setAttribute('id', name);
    input.setAttribute('value', value);
    return input;
}

EventEditor.prototype.buildFromDiv = function() {
    this.vevent = document.getElementById( this.id );
    if ( ! this.vevent ) {
        alert( "Can't find listing element." );
        return;
    }

    var nodes = this.vevent.getElementsByTagName( "*" );

    for ( var i = 0; i < nodes.length; i++ ) {
        var node = nodes.item(i);
        node.normalize();
        className = node.getClassName();
        if ( className != null && node.firstChild != null ) {
            data = node.firstChild.data;
            switch ( className ) {
                case "summary" : this.name = data; break;
                case "dtstart" : 
                    this.set_dtstart( node.getAttribute( 'title' ) ); break;
                case "dtend" : 
                    this.set_dtend( node.getAttribute( 'title') ); break;
                case "venue" : this.venue = data; break;
                case "locality" : this.locality = data; break;
                case "image" : this.image = data; break;
                case "price" : this.price = data; break;
                case "tickets" : 
                    var a = node.firstChild;
                    this.tickets = a.getAttribute( 'href' ); 
                    break;
                case "language" : this.language = data; break;
                case "restrictions" : this.restrictions = data; break;
                case "description" :
                    inputs = node.getElementsByTagName( 'input' );
                    this.description = decodeURIComponent( inputs[0].value );
                    this.md5sum      = inputs[1].value;
                    this.where       = inputs[2].value;
                    this.title       = inputs[3].value;
                    this.complete    = true;
                    break;
            }
        }
    }
}

// EventEditor.handleEvent( evt 'change' )
//
// This is an implementation of a DOM level 2 EventHandler for 'change' in
// an input field in our form.  
//
// FIXME: this almost certainly won't work in IE
//
// This method handles change (aka onChange) events for our input fields.
// The idea is  to capture user input as soon as it happens to make
// submitting easier, and if we choose to implement it later, per field
// instant editing.
EventEditor.prototype.handleEvent = function( evt ) {
    var node = evt.target;
    if ( evt.type == 'change' ) {
        // For some reason 'this' evaluates to the Event target, so we have to
        // use 'currentEditor' here.
        currentEditor.processFormNode( node );
    } else if ( evt.type == 'focus' ) {
        currentEditor.cal.create();
        currentEditor.cal.show();
    }
    return;
}

EventEditor.prototype.buildNewTitle = function() {
    if ( ! this.complete ) return;
    var y = this.dtstart.getFullYear();
    var m = this.dtstart.getMonth();
    var d = this.dtstart.getDate();
    this.title = this.locality + '/' + this.venue + ' '
               + y + ' ' + m + ' ' + d;
    return this.title;
}

EventEditor.prototype.highlightInput = function( class_name ) {
    var nodes = this.form.getElementsByTagName( 'input' );
    for ( var i = 0; i < nodes.length; i++ ) {
        if ( nodes[i].getAttribute( 'name' ) == class_name ) {
            nodes[i].setAttribute( 'style', 'border: 1px solid red;' );
        }
    }
}

EventEditor.prototype.checkCompleteness = function() {
    var c = true;
    if ( ! this.dtstart ) {
        this.highlightInput( 'starts' );
        c = false;
    }
    if ( ! this.locality ) {
        this.highlightInput( 'locality' );
        c = false;
    }
    if ( ! this.venue ) {
        this.highlightInput( 'venue' );
        c = false;
    }
    if ( c == false ) {
        alert( 'In order to figure out where to put this event '
             + 'we need three fields: "locality", "venue", and "starts"' );
    }
    this.complete = c;
    return c;
}

EventEditor.prototype.save = function() {
    if ( this.lock == true ) return;
    this.lock = true;
    var nodes = this.form.getElementsByTagName( 'input' );
    for ( var i = 0; i < nodes.length; i++ ) {
        this.processFormNode(  nodes[i] );
    }
    var textarea = this.form.getElementsByTagName( 'textarea' );
    this.processFormNode( textarea[0] );
    if ( ! this.checkCompleteness() ) {
        this.lock = false;
        return;
    }
    if ( ! this.title ) {
        this.buildNewTitle();
    }
    var ua = sajax_init_object();
    var callback = this.showAjaxResponse;
    currentEditor = null;
    this.container.replaceChild(  this.feedback, this.form );
    savingEditor = this;
    sajax_do_call( 'wfAjaxReplaceEvent', 
                   [ this.md5sum, this.title, this.toString() ], 
                   callback );
}

EventEditor.prototype.restore = function() {
    if ( this.vevent ) {
        this.container.replaceChild( this.vevent, this.form ); 
    } else {
        this.container.removeChild( this.form );
    }
    currentEditor = null;
}

EventEditor.prototype.replaceHcal = function() {
    this.container = this.vevent.parentNode;
    this.container.replaceChild( this.toForm(), this.vevent );
    return;
}

EventEditor.prototype.addToNewEvents = function() {
    this.container = document.getElementById( 'neweventcontainer' );
    this.container.appendChild( this.toForm() );
    return;
}

EventEditor.prototype.showAjaxResponse = function( request ) {
	if ( request.status != 200 ) {
		alert( "Error: " + request.status + " " 
                + request.statusText + ": " + request.responseText );
		return;
	}
    savingEditor.container.innerHTML = request.responseText;
    savingEditor = null;
    return;
}

function editEvent( id ) {
    var testdiv = document.createElement( 'div' );
    if ( ! testdiv.getClassName ) {
        // FIXME
        help_page = 'http://wikevent.org/en/Help:EventEditor'
        msg = "Unfortunately your browser doesn't seem to correctly<br />"
           + "support the DOM Level 2 specification to which this editor<br />"
           + "was written.  We intend to add support for non-compliant<br />" 
           + "browsers (like Internet Explorer), but this will take some<br />"
           + "time.  Please see the "
           + '<a href="' + help_page + '">help page</a> for details.';
        Tip( msg, CLOSEBTN, true, STICKY, true );
        return;
    }
    if ( currentEditor == null ) {
        var ee = new EventEditor( id );
        ee.buildFromDiv();
        ee.replaceHcal();    
        currentEditor = ee;
    }
    return;
}

function createNewEvent( title, datestring ) {
    var ee = new EventEditor();
    if( title.indexOf( '/' ) != -1 ) {
        var i = title.indexOf( '/' );
        ee.locality = title.substring( 0, i );
        ee.venue = title.substring( i + 1, title.length );
    } else {
        ee.description = '<who>' + title + '</who>';
    }
    ee.from_page = title;
    ee.addToNewEvents();
    currentEditor = ee;
}

