var askvisory = {
    dateFormat: "MMM dd, yyyy",
    timeFormat: "H:mm",
    datePickerOwnFormat: "mm/dd/yy",
    advisor: null,
    // TODO: replace identity with object (now it's integer id)
    identity: null,
    identityIsChargeable: false,
    genericErrorMessage: "Unexpected error. Please contact us or try again later.",
    _timeDiff: 0,
    _offsetFromUtc: 0,
    notify: function(msg, group)
    {
        if(!group) {
            group = 'notice'; // notice|error
        }

        var life   = 'error'     == group ? 6000 : 3000;
        var sticky = 'important' == group ? true : false;

        $("#jGrowl").jGrowl(msg, {group: group, life: life, sticky: sticky});
        return this;
    },
    error: function(msg)
    {
        if(!msg) {
            msg = this.genericErrorMessage;
        }
        this.notify(msg, 'error');
        return this;
    },
    setUtcNow: function(timestamp)
    {
        // set site's timestamp to adjust user's time in case if his computer
        // date/time is set to something really irrelevant

        var localNow = new Date(),
            now      = new Date(parseInt(timestamp) * 1000);

        this._timeDiff = localNow.getTime() - now.getTime();
        // this may give several seconds error (from server-side generation till
        // javascript load), but we can afford that

        return this;
    },
    setOffsetFromUtc: function(offset)
    {
        // sets user's actual offset from UTC, according to site settings
        this._offsetFromUtc = offset;
    },
    redirect: function(url, timeout)
    {
        if(!timeout) {
            timeout = 0;
        }
        // this method is required to workaround IE redirect issue
        // (which only appears sometimes)
        setTimeout(function() {
            window.location.href = url;
        }, timeout);
    },
    reload: function(timeout)
    {
        if(!timeout) {
            timeout = 0;
        }
        setTimeout(function() {
            window.location.reload(true);
        }, timeout);
    }
};

askvisory.calendar = {
    advisor: null,
    options: {
        editable: false,
        header: {
            right: 'month,agendaWeek   today prev,next',
            left: 'title',
            center: ''
        },
        allDaySlot: false,
        allDayDefault: false,
        axisFormat: "H:mm",
        //axisFormat: "h tt",
        timeFormat: "H:mm",
        dateFormat: "MMM dd, yyyy",
        columnFormat: {week: "dddd, MMM d", month: "dddd"},
        slotMinutes: 30,
        defaultEventMinutes: 30,
        minTime: 8,
        maxTime: 22,
        aspectRatio: 1.55,
        defaultView: 'agendaWeek'
    }
};

askvisory.ui = {
    _indicator: false,
    resetForm: function(form)
    {
        form.find("input[type='text'], input[type='radio'], input[type='checkbox']").val("");
        form.find("textarea").val("");
        form.find(".invisible").hide();
    },
    populateForm: function(form, object)
    {
        // Conventional form population. Foreach object key finds form element
        // (by respective class) and sets value, using .val()

        var cls, tmp;
        for(var key in object) {
            if(object.hasOwnProperty(key)) {
                // e.g. if key is "title", class will be "formTitle"
                tmp = key.charAt(0).toUpperCase();
                cls = tmp + key.substr(1);
                form.find('.form' + cls).val(object[key]);
            }
        }
    },
    setupHints: function($container)
    {
        $container.find('input.hint, textarea.hint').focus(function() {
            if(!this.hintMessage) {
                this.hintMessage = $(this).val();
            }
            if(this.hintMessage == $(this).val()) {
                $(this).val('');
            }
        }).blur(function() {
            if('' == $(this).val() && this.hintMessage) {
                $(this).val(this.hintMessage);
            }
        });
    },
    ignoreHints: function($container)
    {
        $container.find('input.hint, textarea.hint').each(function() {
            if(!this.hintMessage || this.hintMessage == $(this).val()) {
                $(this).val('');
            }
        });
    },
    showIndicator: function()
    {
        this._indicator = true;
        $('#ajaxIndicator').show();
        return this;
    },
    hideIndicator: function()
    {
        // indicator is not hided immediately (to avoid glitches when very close events occur)
        // it waits a second, makes sure that it must be really hidden, and only
        // then, hides itself

        this._indicator = false;
        var self = this;
        setTimeout(function() {
            if(!self._indicator) {
                $('#ajaxIndicator').hide();
            }
        }, 500);

        return this;
    },
    setupFormTooltips: function($form)
    {
        var applyQtip = function($element, text)
        {
            $($element).qtip({
                content: text,
                position: {
                    corner: {
                        target: 'rightMiddle',
                        tooltip: 'leftMiddle'
                    }
                },
                style: {
                    name: 'cream', // Inherit from preset style
                    tip:  'leftMiddle'
                },
                show: 'focus',
                hide: 'blur'
            });
        }

        $form.find('input,select,label').each(function(i, element) {
            if($(element).attr('tooltiptext')) {
                applyQtip($(element), $(element).attr('tooltiptext'));
            }
        });
    }
};

askvisory.date = {
    toUtcTimestamp: function(date)
    {
        // transforms date to UTC timestamp, not relying on OS timezone settings
        if(!date instanceof Date) {
            throw new Error("Unexpected date");
        }

        // calculate local time as if timezone was set to UTC
        var local = (date.getTime() / 1000) - (date.getTimezoneOffset() * 60);
        // calculate actual UTC time, using site's timezone
        var timestamp = local - askvisory._offsetFromUtc;

        return timestamp;
    },
    fromUtcTimestamp: function(timestamp)
    {
        // transforms timestamp to new date object, not relying on OS timezone settings
        var date = new Date();
        var time = parseInt(timestamp) + askvisory._offsetFromUtc + (date.getTimezoneOffset() * 60);
        date.setTime(time*1000);
        return date;
    },
    convertTimezone: function(date, timezoneOffset)
    {
        var time = date.getTime() - (askvisory._offsetFromUtc - timezoneOffset) * 1000;
        return new Date(time);
    },
    now: function()
    {
        // this function is required in case if local user date is set to
        // something really weird
        var date = new Date();
        //date.setTime((date.getTime() - askvisory._timeDiff));
        date.setTime(date.getTime() - askvisory._timeDiff + date.getTimezoneOffset() * 60000 + askvisory._offsetFromUtc * 1000);

        return date;
    },
    formatDate: function(date, format)
    {
        if(!format) {
            format = askvisory.dateFormat;
        }
        return $.fullCalendar.formatDate(date, format);
    },
    formatTime: function(date, format)
    {
        if(!format) {
            format = askvisory.timeFormat;
        }
        return askvisory.date.formatDate(date, format);
    },
    isValidDate: function(d)
    {
        if (Object.prototype.toString.call(d) !== "[object Date]") {
            return false;
        } else {
            return !isNaN(d.getTime());
        }
    }
};

(function(){

var fdate    = askvisory.date.formatDate,
    ftime    = askvisory.date.formatTime,
    tsp2date = askvisory.date.fromUtcTimestamp,
    date2tsp = askvisory.date.toUtcTimestamp;

askvisory.users = {
    serviceUrl: '/users/',
    usernameExists: function(username, callback)
    {
        $.get(this.serviceUrl + 'username-exists', {username: username}, function(exists) {

            if(typeof data != 'boolean') {
                askvisory.error();
                return ;
            }
            callback(exists);

        }, 'json');
    }
};

askvisory.ratings = {}; // TODO move here all "rate" methods
askvisory.ratings.ui = {
    weights: {
        answers: 0.7,
        breadth: 0.1,
        depth:   0.2
    },
    answers: {
        1: 'None',
        2: 'None',
        3: 'Partly answered',
        4: 'Partly answered',
        5: 'Partly answered',
        6: 'Answered Most',
        7: 'Answered Most',
        8: 'Answered Most',
        9: 'Answered All',
        10: 'Answered All'
    },
    breadth: {
        1:  'Poor Breadth',
        2:  'Poor Breadth',
        3:  'Limited Breadth',
        4:  'Limited Breadth',
        5:  'Limited Breadth',
        6:  'Good Breadth',
        7:  'Good Breadth',
        8:  'Good Breadth',
        9:  'Excellent Breadth',
        10: 'Excellent Breadth'
    },
    depth: {
        1:  'Poor Depth',
        2:  'Poor Depth',
        3:  'Limited Depth',
        4:  'Limited Depth',
        5:  'Limited Depth',
        6:  'Sufficient Depth',
        7:  'Sufficient Depth',
        8:  'Sufficient Depth',
        9:  'Excellent Depth',
        10: 'Excellent Depth'
    },

    showFeedbackDialog: function(title, callback)
    {
        var $ratingDialog = $("#event_feedback_container");

        askvisory.ui.resetForm($ratingDialog);

        // activate sliders
        $ratingDialog.find('.slider').slider({max: 10, min: 1, step: 1});
        $ratingDialog.find('.slider').slider('option', 'value', 6);

        var self = this;

        var getSliderValues = function()
        {
            return {
                answers: $ratingDialog.find('.answersSlider').slider("option", "value"),
                depth:   $ratingDialog.find('.depthSlider').slider("option", "value"),
                breadth: $ratingDialog.find('.breadthSlider').slider("option", "value")
            };
        }

        var calcTotal = function()
        {
            var values = getSliderValues(),
                total  = 0.0;

            for(var key in values) {
                if(values.hasOwnProperty(key)) {
                    total += values[key] * self.weights[key];
                }
            }

            $ratingDialog.find('.score').html(total.toFixed(2));
        }

        var initHints = function(type)
        {
            var setHint = function(value) {
                var msg = self[type][value];
                $ratingDialog.find('.' + type + 'Hint').val(value + ' - ' + msg);
            }

            $ratingDialog.find('.' + type + 'Slider').unbind('slide').bind('slide', function(event, ui) {
                setHint(ui.value);
                setTimeout(calcTotal, 100);
                // settimeout required because at this point slider has OLD value
                // (prior to slide). Cannot use slidestop, becaue it won't update
                // total value at runtime
            });
            // init start value
            setHint(6);
            calcTotal();
        }

        initHints('answers');
        initHints('breadth');
        initHints('depth');

        $ratingDialog.dialog({
            modal: true,
            title: title,
            width: 400,
            close: function() {
                $ratingDialog.dialog("destroy");
                $ratingDialog.hide();
            },
            buttons: {
                "Close Window": function() {
                    $ratingDialog.dialog("close");
                },
                "Submit": function() {
                    var $inputs = $ratingDialog.find('input, textarea');
                    var values = {};
                    $inputs.each(function() {
                        values[this.name] = $(this).val();
                    });

                    $.extend(values, getSliderValues());

                    callback(values, $ratingDialog);
                }
            }
        }).show();

        $(window).resize().resize(); //fixes a bug in modal overlay size ??
    }
};

askvisory.appointments = {
    // Defines operations with appointments: book, reschedule, cancel, decline, etc
    // This object contains service, but nothing related to view. Special object
    // askvisory.appointments.ui is used as controller
    serviceUrl: '/appointments/',

    normalize: function(apps)
    {
        var normalized = [];
        var self = this;
        $.each(apps, function(index, app) {
            normalized.push(self._normalize(app));
        });
        return normalized;
    },

    _normalize: function(app)
    {
        if(app.start) {
            app.start = tsp2date(app.start);
        }
        if(app.end) {
            app.end = tsp2date(app.end);
        }

        if(!app.title) {
            app.title = ''; // title is required
        }
        
        if(app.altStartTime1) {
            app.altStartTime1 = tsp2date(app.altStartTime1);
        }
        if(app.altStartTime2) {
            app.altStartTime2 = tsp2date(app.altStartTime2);
        }
        if(app.altStartTime3) {
            app.altStartTime3 = tsp2date(app.altStartTime3);
        }

        return app;
    },

    _refresh: function(app, data)
    {
        data = this._normalize(data);

        // apply changes to calEvent
        for(var key in data) {
            if(data.hasOwnProperty(key)) {
                app[key] = data[key];
            }
        }

        this.onRefresh(app);

        return app;
    },

    _toAjaxParams: function(app)
    {
        // clone appointment to get params, which we can modify and send to server
        var params = jQuery.extend({}, app);
        params.start = date2tsp(params.start);
        params.end   = date2tsp(params.end);

        if(params.altStartTime1) {
            params.altStartTime1 = date2tsp(params.altStartTime1);
        }
        if(params.altStartTime2) {
            params.altStartTime2 = date2tsp(params.altStartTime2);
        }
        if(params.altStartTime3) {
            params.altStartTime3 = date2tsp(params.altStartTime3);
        }

        return params;
    },

    getPartnerId: function(app)
    {
        if(!askvisory.identity || !app.seekerId || !app.giverId) {
            throw new Error("Cannot resolve other user id");
        }

        if(app.seekerId == askvisory.identity) {
            return app.giverId;
        } else if(app.giverId == askvisory.identity) {
            return app.seekerId;
        } else {
            throw new Error("Cannot resolve other side id");
        }
    },

    loadMyAppointments: function(callback)
    {
        // this function shows client's appointments on advisor's calendar
        var self = this;
        $.get(this.serviceUrl + 'my', {}, function(data) {
            if(data.constructor != Array) {
                return ;
            }
            callback(self.normalize(data));
        }, 'json');
    },

    loadParticipantInfo: function(app, userId, callback)
    {
        $.get(this.serviceUrl + 'get-participant-info', {id: app.id, userId: userId}, function(data) {
            if(!data.offsetFromUtc) { // this is error, but just don't show user info in such case
                return ;
            }
            callback(data);
        }, 'json');
    },

    // own methods:
    reschedulable: function(app)
    {
        if(!app.statusId || app.statusId >= 5 || app.statusId <= 0) {
            // only "opened" (1), "confirmed" (2), "rescheduled" (3) and rejected (4)
            // appointments can be rescheduled
            return false;
        }
        return true;
    },

    decline: function(app, reason, callback)
    {
        this._exec(app, 'decline', {id:app.id, comments: reason}, callback);
    },

    cancel: function(app, reason, callback)
    {
        this._exec(app, 'cancel', {id:app.id, comments: reason}, callback);
    },

    book: function(app, advisor, callback)
    {
        var params = this._toAjaxParams(app);
        params.advisor = advisor;

        this._exec(app, 'book', params, callback);
    },

    accept: function(app, slot, callback)
    {
        this._exec(app, 'accept', {id:app.id, slot:slot}, callback);
    },

    reschedule: function(app, callback)
    {
        if(!this.reschedulable(app)) {
            askvisory.error("You cannot reschedule this appointment");
            return false;
        }

        var params = {
            id: app.id,
            duration: app.duration
        };

        if(app.altStartTime1) {
            params.altStartTime1 = date2tsp(app.altStartTime1);
        }
        if(app.altStartTime2) {
            params.altStartTime2 = date2tsp(app.altStartTime2);
        }
        if(app.altStartTime3) {
            params.altStartTime3 = date2tsp(app.altStartTime3);
        }

        this._exec(app, 'reschedule', params, callback)
        return true;
    },

    acceptReschedule: function(app, slot, callback)
    {
        this._exec(app, 'accept-reschedule', {id:app.id, slot: slot}, callback);
    },

    rate: function(app, rating, callback, actionName)
    {
        var self   = this,
            params = $.extend({}, rating, {id: app.id});

        if(!actionName) {
            actionName = 'rate';
        }

        $.post(this.serviceUrl + 'rate', params, function(data) {
            // This action returns new rating, not appointment
            $.extend(rating, data);
            // FIXME: this should come from serveri
            app.statusId = 6; // rated
            self.afterAction(app, actionName);
            if(callback) callback();
        }, 'json');
    },

    redial: function(app, callback) {
        this._exec(app, 'redial', {id:app.id}, callback);
    },

    _exec: function(app, action, params, callback)
    {
        if(!this.serviceUrl) {
            throw new Error("Service url is not set");
        }

        if(!this._runningNow) {
            this._runningNow = {};
        }

        if(this._runningNow[app.id]) {
            // askvisory.notify("Operation in progress, please wait.");
            return ;
        }

        this._runningNow[app.id] = true;

        var self = this;
        $.post(this.serviceUrl + action, params, function(data) {
            self._runningNow[app.id] = false;
            self._refresh(app, data);

            self.afterAction(app, action);

            if(callback) callback();
        }, 'json');

        $().ajaxError(function(e, xhr) {
            self._runningNow[app.id] = false;
        });
    },

    onRefresh: function(calEvent)
    {},
    afterAction: function(app, action)
    {}
};

// This object represents controller for appointments UI.
// view part is located at separate HTML template file (and included on the page)
askvisory.appointments.ui = {
    statuses: {
        '1': 'New, waiting confirmation',
        '2': 'Confirmed',
        '3': 'Rescheduled, waiting confirmation',
        '4': 'Rejected', // not used anymore
        '5': 'Completed',
        '6': 'Rated'
    },

    timeSlots: {
        init: function($dialog, app, partner) {
            var self = this;

            $dialog.find('.date_picker, .startHour, .startMinute').val("");
            $dialog.find('.partnerTime, .partnerStatus').html("");

            // setup date picker
            $dialog.find('.date_picker').datepicker({
                rangeSelect: false,
                minDate: 0,
                dateFormat: askvisory.datePickerOwnFormat
            });

            $dialog.find('.formDuration').unbind('change');

            // setup listeners for change of any possible time options:
            if(!partner) {
                // have to get partner info dynamically via ajax first
                var userId = askvisory.appointments.getPartnerId(app);
                askvisory.appointments.loadParticipantInfo(app, userId, function(partner) {
                    partner.id = userId;
                    // setup listeners for change of any possible time options
                    $dialog.find('.timeOption').each(function(i, container) {
                        self.listenTimeChange($(container), $dialog, partner);
                    });
                });
            }
            else {
                $dialog.find('.timeOption').each(function(i, container) {
                    self.listenTimeChange($(container), $dialog, partner);
                });
            }
        },

        initAccept: function(app, $container, callback)
        {
            switch(app.statusId) {
                case 1: // new
                    if(app.giverId && app.giverId == askvisory.identity) {
                        $container.find('.accept').show();
                    }
                    else {
                        $container.find('.accept').hide();
                    }
                    $container.find('.confirm_time').show();
                    break;
                case 3: // rescheduled
                    if(app.rescheduledUserId && app.rescheduledUserId != askvisory.identity) {
                        $container.find('.accept').show();
                    }
                    else {
                        $container.find('.accept').hide();
                    }
                    $container.find('.confirm_time').show();
                    break;
                case 2: // confirmed
                default: // other
                    $container.find('.current_time').show();
                    break;
            }

            $container.find('.accept').unbind('click').click(function() {
                var slot;
                switch(true) {
                    case $(this).hasClass('accept-alt1'):
                        slot = 1;
                        break;
                    case $(this).hasClass('accept-alt2'):
                        slot = 2;
                        break;
                    case $(this).hasClass('accept-alt3'):
                        slot = 3;
                        break;
                    default:
                        return false;
                }
                if(3 == app.statusId) { // accept reschedule
                    askvisory.appointments.acceptReschedule(app, slot, callback)
                }
                else { // accept new
                    askvisory.appointments.accept(app, slot, callback);
                }
            });
        },


        listenTimeChange: function($container, $dialog, advisor) {
            var self = this;
            var handler = function() {
                self.updatePartnerTimeAndStatus(advisor, $container, $dialog);
            };
            $container.find('.date_picker, .startHour, .startMinute').unbind('change').change(handler);
            $dialog.find('.formDuration').change(handler);
        },

        getTimeFromOption: function($container) {
            var $date   = $container.find('.date_picker'),
                $hour   = $container.find('.startHour'),
                $minute = $container.find('.startMinute');

            var date = $.datepicker.parseDate(askvisory.datePickerOwnFormat, $date.val());

            if(date) {
                date.setHours(parseInt($hour.val()), parseInt($minute.val()));
                return date;
            }

            return null;
        },

        updatePartnerTimeAndStatus: function(partner, $container, $dialog) {
            var $date = $container.find('.date_picker'),
                $hour = $container.find('.startHour'),
                $minute = $container.find('.startMinute'),
                $duration = $dialog.find('.formDuration:checked'), // duration is same for all options
                $partnerTime = $container.find('.partnerTime'),
                $partnerStatus = $container.find('.partnerStatus');

            if(!$date.val() || !$hour.val() || !$minute.val()) {
                $partnerTime.html('');
                $partnerStatus.html('').removeClass('error success');
            }
            else {
                var date = $.datepicker.parseDate(askvisory.datePickerOwnFormat, $date.val())
                date.setHours(parseInt($hour.val()), parseInt($minute.val()));

                var partnerTime = askvisory.date.convertTimezone(date, partner.offsetFromUtc);
                $partnerTime.html(fdate(partnerTime, "H:mm"));

                // TODO: replace with service all
                var duration = $duration.val(); // duration is in minutes
                var endTime  = (duration ? date2tsp(date) + (duration * 60): '');

                $partnerStatus.html('checking...').removeClass('error success');

                $.post(
                    '/appointments/checktime',
                    {userId: partner.id, startTime: date2tsp(date), endTime: endTime},
                    function(data) {
                        if(data.status) {
                            $partnerStatus.html('').addClass('success'); // intentionally showing empty string
                        }
                        else {
                            $partnerStatus.html('unavailable').addClass('error');
                            if(data.error) {
                                askvisory.error(data.error);
                            }
                        }
                    },
                    'json'
                );
            }
        },
        updateAppointmentTimeslots: function(app, $dialog) {

            var altStartTime1 = this.getTimeFromOption($dialog.find('.altStartTime1')),
                altStartTime2 = this.getTimeFromOption($dialog.find('.altStartTime2')),
                altStartTime3 = this.getTimeFromOption($dialog.find('.altStartTime3')),
                duration      = $dialog.find('.formDuration:checked').val(); // duration is in minutes

            // compatibility with calendar:
            var start = altStartTime1 || altStartTime2 || altStartTime3;

            // validation:
            if(!start) {
                askvisory.error("You didn't propose time. Fill at least one time slot.");
                return false;
            }
            if(!duration) {
                askvisory.error("Please, estimate call duration.");
                return false;
            }
            
            duration = parseInt(duration) * 60 // app must have it in seconds;

            $.extend(app, {
                start: start,
                end: new Date(start.getTime() + (duration * 1000)),
                altStartTime1: altStartTime1,
                altStartTime2: altStartTime2,
                altStartTime3: altStartTime3,
                duration: duration
            });

            return true;
        }
    },

    _onStartTimeChange: function($startTimeField, $endTimeField)
    {
        var $endTimeOptions = $endTimeField.find("option"),
            startTime       = $startTimeField.find(":selected").val(),
            currentEndTime  = $endTimeField.find("option:selected").val();

        // if new start time is after end time - set new end time
        if(!currentEndTime || startTime > currentEndTime) {
            $.each($endTimeOptions, function() {
                if($(this).val() > startTime) {
                    $(this).attr("selected", "selected");
                    return false; // returning "false" is break
                }
            });
        }
    },

    _onEndTimeChange: function($startTimeField, $endTimeField)
    {
        var $endTimeOptions = $endTimeField.find("option"),
            startTime       = $startTimeField.find(":selected").val(),
            currentEndTime  = $endTimeField.find("option:selected").val();

        var dateStart = new Date(startTime),
            dateEnd   = new Date(currentEndTime);

        var durationHours = (dateEnd.getTime() - dateStart.getTime()) / (3600 * 1000);

        if(durationHours > 1) {
            askvisory.error("Maximum schedule time is one hour");
            $.each($endTimeOptions, function() {
                if($(this).val() > startTime) {
                    $(this).attr("selected", "selected");
                    return false; // returning "false" is break
                }
            });
        }
    },

    _toFormValues: function(app)
    {
        var values = $.extend({}, app, {
            status: app.statusId ? this.statuses[app.statusId] : '',
            duration: app.duration / 60
        });

        if(app.start) {
            values.date  = fdate(app.start);
            values.start = ftime(app.start);
        }
        if(app.end) {
            values.end = ftime(app.end);
        }
        if(app.altStartTime1) {
            values.altDate1 = fdate(app.altStartTime1);
            values.altTime1 = ftime(app.altStartTime1);
        }
        if(app.altStartTime2) {
            values.altDate2 = fdate(app.altStartTime2);
            values.altTime2 = ftime(app.altStartTime2);
        }
        if(app.altStartTime3) {
            values.altDate3 = fdate(app.altStartTime3);
            values.altTime3 = ftime(app.altStartTime3);
        }

        return values;
    },

    _getActionButtonsForGiver: function(app)
    {
        var buttons = {}, self = this;

        if(askvisory.appointments.reschedulable(app)) {
            buttons["Reschedule"] = function() {
                self.showRescheduleDialog(app);
            };
        }

        switch(app.statusId) {
            case 1:
                buttons["Decline"] = function() {
                    self.showDeclineDialog(app);
                };
                // There is also "accept" button, but is handled separately
                break;
            case 5:
            case 6:
                break;
            case 2:
                if((new Date()).getTime() < app.start.getTime()) {
                    buttons["Cancel Call"] = function() {
                        self.showCancelDialog(app);
                    };
                }
                break;
            default:
                buttons["Cancel Call"] = function() {
                    self.showCancelDialog(app);
                };
        }

        return buttons;
    },

    _getActionButtonsForSeeker: function(app, $dialogContent)
    {
        var buttons = {},
            self = this;

        switch(app.statusId) {
            case 5: // completed (but no feedback yet)
                buttons["Provide Feedback"] = function() {
                    self.showFeedbackDialog(app);
                };
                buttons["No Feedback"] = function() {
                    var feedback = {
                        answers: 7,
                        breadth: 7,
                        depth: 7,
                        feedback: "No feedback"
                    };
                    askvisory.appointments.rate(app, feedback, function() {
                        if(self.detailsDialog) {
                            self.detailsDialog.dialog("close");
                        }
                    }, 'rate-auto');
                };
                // break is ommited intentionally
            case 6: // completed and rated
                buttons["Book Next Call"] = function() {
                    askvisory.appointments.ui.showBookDialog();
                    if(self.detailsDialog) {
                        self.detailsDialog.dialog("close");
                    }
                };
                break;
            case 2:
                var t = (new Date()).getTime()
                if(askvisory.appointments.reschedulable(app)) {
                    buttons["Reschedule"] = function() {
                        self.showRescheduleDialog(app);
                    };
                }
                if(app.start.getTime() < t && t < (app.end.getTime() + 120 * 60 * 1000)) { // allow redial 120 minutes after scheduled appointment end
                    buttons["Redial Advisor Now"] = function() {
                        askvisory.appointments.redial(app, function() {
                            askvisory.notify("Redialing now...");
                        });
                    };
                }
                if(t < app.start.getTime()) {
                    buttons["Cancel Call"] = function() {
                        self.showCancelDialog(app);
                    };
                }
                break;
            default:
                if(askvisory.appointments.reschedulable(app)) {
                    buttons["Reschedule"] = function() {
                        self.showRescheduleDialog(app);
                    };
                }
                buttons["Cancel Call"] = function() {
                    self.showCancelDialog(app);
                };
        }

        return buttons;
    },

    getActionButtons: function(calEvent)
    {
        var buttons = {},
            self = this;

        if(centerPopup && typeof centerPopup == 'function') {
            buttons["Message User"] = function() {
                // FIXME: messaging uses legacy code - remove when possible

                // Need to close dialog prior to showing legacy popups:
                // because dialog is modal and legacy popups won't work when
                // modal dialog is active
                if(self.detailsDialog) {
                    self.detailsDialog.dialog('close');
                }

                var $sendPopup = $('#composeMailPop');
                askvisory.ui.resetForm($sendPopup);
                $sendPopup.find('textarea.wysiwyg').wysiwyg('setContent', '');

                var username = self._getPartnerScreenName(calEvent);
                $('#messageTo').val(username).attr('readonly', true);

                centerPopup();
                loadPopup();
                $("#composeMailPop").css("display","inline");
            };
        }

        if (!askvisory.identity || !calEvent.statusId || 0 > calEvent.statusId || calEvent.statusId > 6) {
            return buttons;
        }
        // Prepare buttons for details dialog, depending on two things:
        // 1. Who has requested details: giver or seeker
        // 2. Current appointment status

        if(askvisory.identity == calEvent.giverId) {
            $.extend(buttons, this._getActionButtonsForGiver(calEvent));
        } else if (askvisory.identity == calEvent.seekerId) {
            $.extend(buttons, this._getActionButtonsForSeeker(calEvent));
        } else {
            return {}; // should nevevr happen
        }

        // common buttons ragrdles of status:
        // TODO: don't show on calls which already happen (there is call duration)
        var self = this;
        return buttons;
    },

    showFeedbackDialog: function(calEvent)
    {
        var title = "Feedback for " + calEvent.giverName,
            self  = this;

        askvisory.ratings.ui.showFeedbackDialog(title, function(feedback, $ratingDialog) {
            askvisory.appointments.rate(calEvent, feedback, function() {
                // self._refreshEvent(calEvent); // FIXME: this method doesn't exist anymore, use other?
                $ratingDialog.dialog("close");
                if(self.detailsDialog) {
                    self.detailsDialog.dialog("close");
                }
            });
        });
    },

    showCancelDialog: function(calEvent)
    {
        var $cancelDialog = $("#event_cancel_container");
        askvisory.ui.resetForm($cancelDialog);

        $cancelDialog.find('select').val('');

        var self = this;
        $cancelDialog.dialog({
            modal: true,
            title: "Cancel Appointment - " + calEvent.title,
            close: function() {
                $cancelDialog.dialog("destroy");
                $cancelDialog.hide();
                self.onDialogClose($cancelDialog);
            },
            buttons: {
                "Close Window": function() {
                    $cancelDialog.dialog("close");
                },
                "Confirm Cancel": function(){
                    var reason = $cancelDialog.find('textarea[name="comments"]').val();
                    askvisory.appointments.cancel(calEvent, reason, function() {
                        $cancelDialog.dialog("close");
                        if(self.detailsDialog) {
                            self.detailsDialog.dialog("close");
                        }
                    });
                }
            }
        }).show();

        $(window).resize().resize(); //fixes a bug in modal overlay size ??
    },

    showDeclineDialog: function(calEvent)
    {
        var $declineDialog = $("#event_decline_container");
        askvisory.ui.resetForm($declineDialog);

        $declineDialog.find('select').val('');

        var self = this;
        $declineDialog.dialog({
            modal: true,
            title: "Decline Appointment - " + calEvent.title,
            close: function() {
                $declineDialog.dialog("destroy");
                $declineDialog.hide();
                self.onDialogClose($declineDialog);
            },
            buttons: {
                "Decline Call": function(){
                    var reason = $declineDialog.find('textarea[name="comments"]').val();
                    askvisory.appointments.decline(calEvent, reason, function() {
                        $declineDialog.dialog("close");
                        if(self.detailsDialog) {
                            self.detailsDialog.dialog("close");
                        }
                    });
                },
                "Close Window": function(){
                    $declineDialog.dialog("close");
                }
            }
        }).show();

        $(window).resize().resize(); //fixes a bug in modal overlay size ??
    },

    showBookDialog: function(app)
    {
        if(!app) {
            app = {start: new Date(), end: new Date()};
        }

        var advisor = askvisory.advisor;
        var $bookDialog = $("#event_new_container");

        // reset form:
        $bookDialog.find('.formDescription, .formPrivate, .formTitle').val("");

        this.timeSlots.init($bookDialog, app, advisor);

        // show advisor's phone rate
        var value = advisor.phoneRate / 60.0;
        $bookDialog.find('.rateValue').html('$' + value.toFixed(2) + ' per minute');

        var updatePaymentNotice = function () {
            // also query for the client's info
            $.post('/appointments/get-identity-info', {}, function(data) {
                if(data.paymentAccountName) {
                    var $notice = $bookDialog.find('.paymentNotice');
                    $notice.find('.paymentAccountName').html(data.paymentAccountName);
                    $notice.css({visibility: 'visible'});
                    // $notice.fadeIn();
                }
            }, 'json');
        }
        updatePaymentNotice();
        var interval = setInterval(updatePaymentNotice, 10000);

        var self = this;
        $bookDialog.dialog({
            modal: true,
            title: "New Appointment",
            width: 500,
            close: function() {
                clearInterval(interval);
                $bookDialog.dialog("destroy");
                $bookDialog.hide();
                self.onDialogClose($bookDialog);
            },
            buttons: {
                "Cancel": function() {
                    $bookDialog.dialog("close");
                },
                "Book Call": function(e) {
                    if(!askvisory.appointments.ui.timeSlots.updateAppointmentTimeslots(app, $bookDialog)) {
                        return false;
                    }

                    $.extend(app, {
                        title:       $bookDialog.find('.formTitle').val(),
                        description: $bookDialog.find('.formDescription').val(),
                        isPrivate:   $bookDialog.find('.formPrivate').attr('checked') ? 1 : 0
                    });

                    if(e.target) {
                        $(e.target).text('Submitting - Please Wait').attr('disabled', 'disabled');
                    }

                    askvisory.appointments.book(app, advisor.screenName, function() {
                        if(!askvisory.identityIsChargeable) {
                            // after booking an appointment, user must be redirected to confirm his
                            // payment system
                            w = window.location;
                            window.location = '/payments/first?redirect='
                                            + encodeURIComponent(
                                                  w.pathname
                                                + (w.search.length ? w.search : '?a=1')
                                                + '&timestamp=' + askvisory.date.toUtcTimestamp(app.start)
                                            );
                        }

                        if(e.target) {
                            $(e.target).text('Book Call').removeAttr('disabled');
                        }

                        $bookDialog.dialog("close");
                        self.onBook(app);
                    });
                }
            }
        }).show();
    },

    _getPartnerScreenName: function(app)
    {
        var withScreenName;
        if(askvisory.identity == app.giverId) {
            withScreenName = app.seekerName;
        } else {
            withScreenName = app.giverName;
        }
        return withScreenName;
    },

    showDetailsDialog: function(app)
    {
        if(!askvisory.identity || !app.statusId) {
            return ;
        }

        var $dialog = this.detailsDialog = $("#event_view_container");

        askvisory.ui.resetForm($dialog);
        askvisory.ui.populateForm($dialog, this._toFormValues(app));

        this.timeSlots.initAccept(app, $dialog, function(){$dialog.dialog('close')});

        var self = this;
        var withScreenName = this._getPartnerScreenName(app);

        this.detailsDialog.dialog({
            modal: true,
            title: "Appointment with " + withScreenName,
            width: 400,
            close: function() {
                self.detailsDialog.dialog("destroy");
                self.detailsDialog.hide();
                self.onDialogClose(this.detailsDialog);
            },
            buttons: this.getActionButtons(app)
        }).show();

        $(window).resize().resize(); //fixes a bug in modal overlay size ??
    },

    showRescheduleDialog: function(calEvent)
    {
        var $dialogContent = $("#event_reschedule_container");
        this.timeSlots.init($dialogContent, calEvent);

        var self = this;

        $dialogContent.dialog({
            modal: true,
            title: "Rescheduling: " + calEvent.title,
            width: 500,
            close: function() {
                $dialogContent.dialog("destroy");
                $dialogContent.hide();
                self.onDialogClose(this.detailsDialog);
            },
            buttons: {
                "Close Window": function() {
                    $dialogContent.dialog("close");
                },
                Reschedule: function() {
                    if(!askvisory.appointments.ui.timeSlots.updateAppointmentTimeslots(calEvent, $dialogContent)) {
                        return false;
                    }

                    askvisory.appointments.reschedule(calEvent, function(data) {
                        $dialogContent.dialog("close");
                        if(self.detailsDialog) {
                            self.detailsDialog.dialog("close");
                        }
                    });
                }
            }
        }).show();

        $(window).resize().resize(); //fixes a bug in modal overlay size ??
    },

    onDialogClose: function(dialog)
    {},

    onBook: function(app)
    {},

    _initPartnerInfoDisplay: function(partner, $dialog)
    {
        var offset    = partner.offsetFromUtc,
            phoneRate = partner.phoneRate;

        var $userTimeContainer = $dialog.find('.partnerTime'),
            $startElem         = $dialog.find('.formStart'),
            $endElem           = $dialog.find('.formEnd'),
            $dateElem          = $dialog.find('.formDate');

        $userTimeContainer.hide();

        $startElem.unbind('change');
        $endElem.unbind('change');
        if($dateElem) $dateElem.unbind('change');

        var self = this;

        var refresh = function() {
            var date = $dateElem.length
                     ? self._getActualTime($dateElem, $startElem)
                     : new Date($startElem.val());

            if(!date instanceof Date || !askvisory.date.isValidDate(date)) {
                $userTimeContainer.find('input').val('');
                return ;
            }

            var local = askvisory.date.convertTimezone(date, offset);
            $userTimeContainer.find('input').val(fdate(local, "MMM dd, H:mm"));
        }

        refresh();
        $startElem.change(function() {
            refresh();
            self._onStartTimeChange($startElem, $endElem);
        });
        $endElem.change(function() {
            refresh();
            self._onEndTimeChange($startElem, $endElem);
        });
        if($dateElem.length) $dateElem.change(refresh);

        $userTimeContainer.show();

        // also setup advisor phone rate for display
        var $advisorRateContainer = $dialog.find('.advisorRate');
        $advisorRateContainer.hide();

        if(phoneRate) {
            var value = phoneRate / 60.0;
            $advisorRateContainer.show().find('.rateValue').html('$' + value.toFixed(2) + ' per minute')
        }
    },

    _getActualTime: function(datePicker, timeSelect)
    {
        // returns actual date object, built from two form elements:
        // datePicker and time select

        var realDate = $.datepicker.parseDate(askvisory.datePickerOwnFormat, datePicker.val());
        var tmpTime  = new Date(timeSelect.val());
        realDate.setHours(tmpTime.getHours());
        realDate.setMinutes(tmpTime.getMinutes());

        return realDate;
    },

    showInlineControls: function(appointments, $container, redirectUrl)
    {
        // initialize appointment buttons
        var index = {};
        // hash appointments for convenience:
        $.each(appointments, function(unused, appointment) {
            index[appointment.id] = appointment;
        });

        var self     = this;
        var template = [
            '<a href="#" class="av-button %CLASS% lookup%NUM% ui-state-default ui-corner-all">',
                '%ICON%',
                '%LABEL%',
            '</a>'
        ].join('');

        $($container).find('.buttons').each(function() {
            var id = parseInt($(this).find('span').attr('title')),
                $container = $(this),
                buttons    = self.getActionButtons(index[id]);

            var i = 0, cssClass = '', iconClass = '';
            for(var label in buttons) {
                if(buttons.hasOwnProperty(label)) {
                    cssClass  = label.replace(/\s/g,'');
                    iconClass = '';

                    switch(cssClass) {
                        case 'MessageUser':iconClass = 'ui-icon-mail-closed';break;
                        case 'Reschedule': iconClass = 'ui-icon-arrowreturnthick-1-e';break;
                        case 'CancelCall': iconClass = 'ui-icon-cancel';break;
                        case 'Decline':    iconClass = 'ui-icon-cancel';break;
                        case 'ProvideFeedback': iconClass = 'ui-icon-star';break;
                        case 'NoFeedback':      iconClass = 'ui-icon-minus';break;
                        case 'BookNextCall':    iconClass = 'ui-icon-calculator';break;
                        case 'RedialAdvisorNow': iconClass = 'ui-icon-signal-diag';break;
                        default:
                            iconClass = '';
                    }
                    if(iconClass.length) {
                        cssClass += ' av-button-icon-left';
                    }
                    var html = template.replace('%CLASS%', cssClass)
                                       .replace('%LABEL%', label)
                                       .replace('%ICON%', iconClass.length ? '<span class="ui-icon '+iconClass+'"></span>' : '')
                                       .replace('%NUM%', i);
                    $container.append(html);
                    $container.find('.lookup'+i).click(buttons[label]);
                    i++;
                }
            }
        });

        var qtipParams = {
            position: {
                corner: {
                    target: 'leftMiddle',
                    tooltip: 'rightMiddle'
                }
            },
            style: {
                name: 'cream', // Inherit from preset style
                tip:  'rightMiddle'
            },
            // show: 'mouseover',
            hide: {
                fixed: false
            }
        };


        qtipParams.content = 'Our systems will automatically connect you to the advisor. However, if the call drops for any reason – you will need to press this button to restart the call. (The advisor can not initiate a redial)';
        $container.find('.RedialAdvisorNow').qtip(qtipParams);

        this.initDashboardActions(redirectUrl);
    },

    initDashboardActions: function(redirectUrl) {

        // prepare action handlers
        askvisory.appointments.afterAction = function(calEvent, action) {
            switch(action) {
                case 'book':
                    askvisory.notify("Appointment has been requested");
                    break;
                case 'cancel':
                    askvisory.notify("Appointment has been canceled");
                    break;
                case 'decline':
                    askvisory.notify("Appointment has been declined");
                    break;
                case 'reschedule':
                    askvisory.notify("Appointment has been rescheduled");
                    break;
                case 'accept-reschedule':
                    askvisory.notify("Appointment reschedule has been accepted");
                    break;
                case 'rate':
                    askvisory.notify("Your feedback was saved. Thank you!");
                    break;
                case 'rate-auto':
                    askvisory.notify("Saved. Thank you!");
                    break;
                case 'accept':
                    askvisory.notify("The meeting has been confirmed!");
                    break;
                default:
            }
            if(redirectUrl) {
                askvisory.redirect(redirectUrl, 800);
            }
        };
    }
}

askvisory.projects = {
    serviceUrl: '/projects/',
    rate: function(proposal, rating, callback)
    {
        var params = $.extend({}, rating, {id: proposal.id});

        $.post(this.serviceUrl + 'rate', params, function(data) {
            // This action returns new rating, not appointment
            $.extend(rating, data);
            if(callback) callback();
        }, 'json');
    }
};

askvisory.projects.ui = {
    showFeedbackDialog: function(proposal, project)
    {
        var title = "Feedback for " + proposal.advisorName;

        askvisory.ratings.ui.showFeedbackDialog(title, function(feedback, $ratingDialog) {
            askvisory.projects.rate(proposal, feedback, function() {
                $ratingDialog.dialog("close");
                askvisory.notify("Your feedback is saved. Thank you!");
                askvisory.reload(500);
            });
        });

        $(window).resize().resize(); //fixes a bug in modal overlay size ??
    }
}

var setupCancelPopup = function(container)
{
    $(container).find("select").change(function(){
        var reason  = $(this).find(":selected").val();
        var comment = $(container).find("textarea");
        if('other' == reason) {
            comment.val('').show().focus();
        } else {
            comment.hide().val(reason);
        }
    });
};

$().ready(function() {
    setupCancelPopup("#event_decline_container");
    setupCancelPopup("#event_cancel_container");

    // setup tooltips
    var applyQtip = function($element, text)
    {
        if($($element).qtip) {
            $($element).qtip({
                content: text,
                position: {
                    corner: {
                        target: 'rightMiddle',
                        tooltip: 'leftMiddle'
                    }
                },
                style: {
                    name: 'cream', // Inherit from preset style
                    tip:  'leftMiddle'
                },
                hide: {
                    fixed: true
                }
            });
        }
    }

    $('.tooltip_node').each(function(i, element) {
        if($(element).attr('tooltiptext')) {
            applyQtip($(element), $(element).attr('tooltiptext'));
        }
        else if($(element).attr('tooltipid')) {
            applyQtip($(element), $('#' + $(element).attr('tooltipid')));
        }
    });

    var textCounter = function(e) {
        if($(this).attr('onkeyup')) { // there is custom on keyup handler
            return ;
        }

        var max = parseInt($(this).attr('maxlength'));

        if(this.value.length >= max) {
            e.preventDefault();
            if(this.value.length > max) {
                this.value = this.value.substring(0, max);
            }
        }

        if($(this).attr('counter')) {
            $($(this).attr('counter')).html(max-this.value.length + " Remaining");
        }
    }

    $('textarea[maxlength]').keyup(textCounter);
    $('textarea[maxlength]').change(textCounter);
})
})();

