/*
 *   V I D I V I C I
 *   M M X ~ M M X I I
 *   Omnia iura reservata.
 */

window.vidivici = {};


// A view's data has this format (N.B. not all fields are always present):
//
//    var view = new vidivici.view({
//        subject: {
//          name: "Vodafone",
//          price: 1.18,
//        },
//        userName: "Jeremy",
//        priceTarget: 2.0,
//        priceWhenExpressed: 1.8,
//        priceTargetDate: 123456876879,
//        confidence: 0.5,
//        credibility: 0.5,
//        weighting: 0.25,
//        lastUpdated: 123456876879,
//        comments: "Blah blah blah"
//    });

vidivici.view = function (data)
{
    this.data = data;
};

vidivici.view.prototype = {

    lookup : [
        [0.0,  0.1,  "You think there’s a", "# thinks there’s a", "you thought there’d be", "# thought there’d be", " a tiny chance ", "Think there’s a tiny chance"],
        [0.1,  0.25, "You’ve", "# has", "you’d", "# had", " a hunch that ", "Just a hunch"],
        [0.25, 0.40, "You think there’s a", "# thinks there’s a", "you thought there’d be", "# thought there’d be", " a fair chance ", "Think there’s a fair chance"],
        [0.40, 0.60, "You’re", "# is", "you were", "# was", " about 50-50 that ", "About 50-50"],
        [0.60, 0.70, "You’re", "# is", "you were", "# was", " pretty sure ", "Pretty sure"],
        [0.70, 0.80, "You’re", "# is", "you were", "# was", " almost certain ", "Almost certain"],
        [0.80, 1.01, "You believe", "# believes", "you believed", "# believed", " ", "Totally confident"]],

    isCommunityView : function() { return !this.data.userName && !this.data.confidence; },
    isMyView : function() { return vidivici.signedInUser && (vidivici.signedInUser.name == this.data.userName); },
    isBrokerView : function() { return this.data.userType && this.data.userType == "B"; },

    userNameToDisplay : function() {
        if (this.isCommunityView()) return "Community View";
        if (this.isMyView()) return "Your View";
        return this.data.userName;
    },

    confidenceInWords : function() {
        var c = this.data.confidence;
        var r = "";
        $.each(this.lookup, function(i, v) {
            if (c < v[1] && c >= v[0]) r = v[7];
        });
        return r + ' (' + Math.round(c * 100.0) + '%)';
    },

    renderInWords : function(subjectName, price)
    {
        var text;

        if (this.isCommunityView()) {
            text = "The community thinks that ";
        } else {
            for (var i = 0; i < this.lookup.length; i++)
            {
                if (this.data.confidence < this.lookup[i][1] && this.data.confidence >= this.lookup[i][0])
                {
                    var lu = this.data.lastUpdated ? new Date(this.data.lastUpdated) : new Date();
                    if (this.isMyView()) {
                        text = this.wasUpdatedToday() ? this.lookup[i][2] : ($.timeago(lu) + " " + this.lookup[i][4]);
                    } else {
                        text = this.wasUpdatedToday() ? this.lookup[i][3] : ($.timeago(lu) + " " + this.lookup[i][5]);
                        text = text.replace("#", this.data.userName);
                    }
                    text += this.lookup[i][6];
                }
            }
        }
        text += subjectName + "’s stock price ";
        text += this.wasUpdatedToday() || this.isCommunityView() ? "will " : "would ";
        if (!this.data.priceWhenExpressed) this.data.priceWhenExpressed = price;
        var change = (this.data.priceTarget - this.data.priceWhenExpressed) / this.data.priceWhenExpressed;
        if (change > 0.05)
        {
            text += "rise by " + Math.round(change * 100) + "% ";
            text += "(to around " + vidivici.util.priceString(this.data.priceTarget) + ") ";
            text += this.timingString(this.data);
        }
        else if (change < -0.005)
        {
            text += "fall by " + Math.round(-change * 100) + "% ";
            text += "(to around " + vidivici.util.priceString(this.data.priceTarget) + ") ";
            text += this.timingString(this.data);
        }
        else text += this.wasUpdatedToday() || this.isCommunityView() ? "stay at around its current level" : ("stay at around " + vidivici.util.priceString(this.data.priceWhenExpressed));

        String.prototype.capitalizeFirstLetter = function() {
            return this.charAt(0).toUpperCase() + this.slice(1);
        }

        return text.capitalizeFirstLetter() + ".";
    },

    wasUpdatedToday : function()
    {
        var now = new Date();
        var lu = this.data.lastUpdated ? new Date(this.data.lastUpdated) : new Date();
        return lu.getTime() > (now.getTime() - (24 * 3600 * 1000)) &&
               lu.getDay() == now.getDay();
    },

    // Only call this for my view
    infConfString : function()
    {
        var ic = this.data.inferredConfidence;
        return ic < (this.data.confidence * 0.95) ?
                ("Things haven’t worked out completely as you predicted so we’re now treating this as having a reduced likelihood of " + Math.round(ic * 100.0) + "%. " +
                 "If you disagree, just revise your view.")
            : null;
    },

    timingString : function(data)
    {
        var timeInFutureMS = data.priceTargetDate - new Date().getTime();
        var months = Math.round(timeInFutureMS / (30 * 24 * 60 * 60 * 1000));
        var ageInDays = (new Date().getTime() - data.lastUpdated) / (24 * 3600 * 1000);

        if (ageInDays < 30) {
            if (months == 0) return "within the next month";
            if (months == 1) return "within the next 2 months";
            var lowerBound = months - 1;
            var upperBound = months + 1;
            return "in the next " + lowerBound + " to " + upperBound + " months";
        } else {
            var months = ["January","February","March","April","May","June","July","August","September","October","November","December"];
            if (months == 0) return "by around now";
            var d = new Date(data.priceTargetDate);
            var s = "by around " + months[d.getMonth()];
            if (d.getYear() == new Date().getYear() + 1) s += " next year";
            else if (d.getYear() > new Date().getYear() + 1) s += " " + d.getYear();
            else s += " this year";
            return s;
        }
    },

    dotColour : function()
    {
        var sliderValue = this.sliderValue();

        var green = 255;
        if (sliderValue < 0) green = (1 + sliderValue) * 255;

        var red = 255;
        if (sliderValue > 0) red = (1 - sliderValue) * 255;

        return (red << 16) + (green << 8); // blue is zero
    },

    // Value between 1 and -1 inclusive (+/-100% pa change in share price)
    sliderValue : function()
    {
        var rating = this.rating();
        var sliderValue = (rating >= 0) ? Math.sqrt(rating) : -Math.sqrt(-rating);
        if (sliderValue < -1) sliderValue = -1;
        if (sliderValue > 1) sliderValue = 1;
        return sliderValue;
    },

    rating : function()
    {
       var delta = this.data.priceTarget - this.data.subject.price;
       var milliSecondsInYear = 1000 * 60 * 60 * 24 * 365;

       var timeIntervalMS = this.data.priceTargetDate - new Date().getTime();
       var timeMultiplier = milliSecondsInYear / timeIntervalMS;
       return (delta / this.data.subject.price) * timeMultiplier;
    },

    url : function()
    {
        return "/#" + this.hash();
    },

    hash : function()
    {
        var viewSel = "";
        if (!this.isCommunityView()) viewSel = "/" + this.data.userName; // Empty for community view
        return "!/" + this.data.subject.ticker + viewSel;
    }

};

// Called by find views box
// TODO Spinner/disable search box until we have a response
vidivici.jumpToSubject = function(ticker)
{
    var data = {subject:{ticker:ticker}};
    if (vidivici.signedInUser) data.userName = vidivici.signedInUser.name;
    var v = new vidivici.view(data);
    if (vidivici.subject) window.location.hash = v.hash();
    else window.location = v.url();
    // Disable the search box so it isn't submitted multiple times (users hitting enter repeatedly)
    $('.navSearch input').autocomplete('disable');
}

// Home page
vidivici.renderPortfolioViews = function(data)
{
    if (data.length == 0) $('#viewsOnPortfolio').hide();
    else $('#viewsOnPortfolio').show();

    $('#viewsOnPortfolio ul').empty();
    $('#viewsOnPortfolio .notice').remove();
    for (var i = 0; i < data.length; i++) {
        var view = new vidivici.view(data[i]);
        // Show prompt when it's the first item and a community view
        var viewHTML = vidivici.renderView(view, (i == 0) && view.isCommunityView());

        $('#viewsOnPortfolio ul').append(viewHTML);
        $('#viewsOnPortfolio .vdot').last().vdot(view.data);
    }                                     
    vidivici.attachBehavioursToViews($('#viewsOnPortfolio ul'));
    vidivici.refreshScrollPanes();

    $('#viewsOnPortfolio ul').css({backgroundImage: 'none'});
    $('#viewsOnPortfolio .syncing li').animate({opacity: "1"}, 600, function() {
        // Once faded in, after a delay show prompt on the first item, if it's a community view
        $('span.vTi').delay(1000).animate({eft: "40px"}, 400);
        $('span.vTi').delay(1000).fadeIn(400);
    });

    // $('#viewsOnPortfolio .syncing .syncingOverlay').fadeOut(300, function() {
    // });
}

vidivici.renderOtherViews = function(data)
{
    if (data.length == 0) {
        var parent = $('#userOtherViews');
        if (!parent.children().hasClass(".notice")) {
            var viewsPrompt =
                '<div class="notice">' +
                '<p class="vLabel vToDo">You can set views on any company, not just ones in your portfolio. When you do they’ll appear here.</p>' +
                '</div>';
            parent.prepend(viewsPrompt);
        }

        var viewExample = 
            '<li class="viewDemo">' +
            '<div class="viewDot"><div class="vdot"><span class="widgetViewThumbnailReloaded"><span><span style="border-color: rgb(120, 255, 0);"><span style="background-color: rgba(120, 255, 0, 0.496094); "><a class="widgetUser" href="#" title="Visual representation of your view"></a></span></span></span></span></div></div><div class="stockInfo"><a class="stockName" title="Link to the company page">Example Company Title</a><span class="ticker" title="Company ticker">(LSE:Ticker)</span><div class="credibilityRangeWrap"><span class="credibilityRange viewCredRate" style="width: 54%"></span><img src="site/im/credibility-star-rating.png" title="The credibility rating"></div><time title="When you posted the view">updated <abbr style="border-bottom: 1px dotted;">about 23mins ago</abbr></time></div>' +
            '</li>' ;
            $('#userOtherViews ul').append(viewExample);

    }
    for (var i = 0; i < data.length; i++) {
        var view = new vidivici.view(data[i]);
        var viewHTML = vidivici.renderView(view, false);

        $('#userOtherViews ul').append(viewHTML);
        $('#userOtherViews .vdot').last().vdot(view.data);
    }
    vidivici.attachBehavioursToViews($('#userOtherViews ul'));
    vidivici.refreshScrollPanes();

    $('#userOtherViews ul').css({backgroundImage: 'none'});
    $('#userOtherViews .syncing li').animate({opacity: "1"}, 600);
    // $('#userOtherViews .syncing .syncingOverlay').fadeOut(300, function() {
      
    // });
}


// Home page and subject page
vidivici.attachBehavioursToViews = function(parent) {
    $('ul.views li').not(".viewDemo").click(function() {
        var viewLink = $(this).children().find('a.stockName').attr("href");
        window.location.href = viewLink;
        $('.modalUserProfile').dialog('close');
    });
    jQuery('abbr.timeago').timeago();
}

// TODO This could be a jquery plugin to turn an element into a view representation (and attach behaviours)
vidivici.renderView = function(view, showSetYourViewTooltip)
{
    var viewHTML = '<li>';
    if (showSetYourViewTooltip) {
        var setViewPrompt = 
        '<div class="notice">' +
        '<p class="vLabel vToDo">Set your own view on <a href="' + view.url() + '">' + view.data.subject.name + '</a></p>'+
        '</div>';
        $('#viewsOnPortfolio').prepend(setViewPrompt);
    }
    viewHTML += '<div class="viewDot"><div class="vdot"></div></div>';

    viewHTML += '<div class="stockInfo"><a class="stockName" href="' + view.url() + '">' + view.data.subject.name + '</a>';
    viewHTML += '<span class="ticker">(' + view.data.subject.ticker + ')</span>';

    viewHTML += '<div class="credibilityRangeWrap">';
    viewHTML += '<span class="credibilityRange viewCredRate" style="width: ' + Math.round(view.data.credibility * 100.0) + '%"></span>';
    viewHTML += '<img src="site/im/credibility-star-rating.png">';
    viewHTML += '</div>';

    var lastUpdatedDate = new Date(view.data.lastUpdated);
    viewHTML += '<time>updated ' + vidivici.util.timeAgoElement(lastUpdatedDate) + '</time>';

    viewHTML += '</div>';


    if (view.data.comments) {
        viewHTML += '<span class="viewComment">' + view.data.comments + '</span>';
    }

    viewHTML += '</li>';
    return viewHTML;

}


// This is the portfolio object model:
//
//    {
//        positions: [
//            {
//                stockName: "Vodafone Group",
//                ticker: "LSE:VOD",
//                shares: 300,
//                stockPrice: 1.40,
//                value: 420.00
//            }
//        ],
//        cashBalance: 2300.50,
//        totalValue: 2720.50,
//        lastSyncTime: 137857348579,
//        lastSyncErrorCode: 0,
//        lastSyncErrorMessage: ""
//    }

vidivici.renderPortfolioSummary = function(data, showUploadPortfolioPrompt)
{   
    var portfolioHTML = "";

    if (showUploadPortfolioPrompt) {
        portfolioHTML = '<div class="notice">';
        var cashBalanceString = '£' + vidivici.util.currencyFormatted(data.cashBalance);
        portfolioHTML +=
            '<p class="vLabel vToDo">To get you going, we’ve given you a starting cash balance of ' + cashBalanceString +
            '. Upload your portfolio and we’ll tailor trade ideas around the investments you’ve made and the cash you have available (just go to “Post a transaction” at the bottom).</p></div>';
    }

    // TODO Attach handler to close button, pass in Lift link
//    $('div.notice a.close').click(function() {
//      $(this).parent().fadeOut(300);
//      return false;
//    });

    var largestPosition = 0;
    for (var i = 0; i < data.positions.length; i++) {
        var position = data.positions[i];
        if (position.value > largestPosition) { largestPosition = position.value; }
    }
    if (data.cashBalance > largestPosition) { largestPosition = data.cashBalance; }

    for (var i = 0; i < data.positions.length; i++) {
        var position = data.positions[i];
        portfolioHTML += '<li><span class="companyWrapper"><a href="#" class="stockName">' + position.stockName + '</a>';
        portfolioHTML += '<span class="ticker">(' + position.ticker + ')</span></span>';
        var width = Math.round((position.value / largestPosition) * 95);
        portfolioHTML += '<div class="portfolioGraphWrapper"><span class="portfolioGraphDisplay" style="width: ' + width + '%"></span></div>';
        var sharesString = position.shares + ' share';
        if (position.shares > 1) sharesString += 's';
        var priceString = vidivici.util.priceString(position.stockPrice);
        portfolioHTML += '<span class="amount">£' + vidivici.util.currencyFormatted(position.value) + '</span>';
        portfolioHTML += '<span class="value">' + sharesString + ' worth ' + priceString + ' each</span></li>';
    }
    var width = Math.round((data.cashBalance / largestPosition) * 95);
    portfolioHTML += '<li class="cash"><span class="stockName" href="#">Cash</span>';
    portfolioHTML += '<div class="portfolioGraphWrapper portfolioCash"><span class="portfolioGraphDisplay" style="width: ' + width + '%"></span></div>';
    portfolioHTML += '<span class="amount">£' + vidivici.util.currencyFormatted(data.cashBalance) + '</span></li>';
    $('#userPortfolio ul').append(portfolioHTML);

    vidivici.refreshScrollPanes();

    $('#userPortfolio .syncing .syncingOverlay').fadeOut(300, function() {
      $('#userPortfolio .syncing li').animate({opacity: "1"}, 600);
    });
}

// Takes as input an object with one value 'riskProportionLimit'.
vidivici.renderRiskSummary = function(data, showRiskLimitPrompt)
{
    if (!$('#userRiskAppetite').hasClass('updated')) {
        $('#userRiskAppetite').removeClass('ui-tabs-hide');
    };

    $('#userRiskAppetite .notice').remove();
    if (showRiskLimitPrompt) {
        var riskLimitPrompt =
            '<div class="notice">' +
            '<p class="vLabel vToDo">To get things started we’ve assumed a default risk appetite here. ' +
            'If this doesn’t sound right, update it and we’ll be able to find more suitable trade ideas for you.</p></div>';
        $('#userRiskAppetite').prepend(riskLimitPrompt);
    }

    var riskPercentageString = Math.round(data.riskProportionLimit * 100) + '%';
    var riskLevelString = vidivici.renderRiskLimitAsText(data.riskProportionLimit).short;
    var riskHTML = '<p><span class="userCurrentRiskAppetite">' + riskLevelString + '</span> <span class="userCurrentRiskAppetitePercentage">(' + riskPercentageString + ')</span></p>';
    $('#userRiskAppetite .riskSummary').html(riskHTML);
    if (!$('#userRiskAppetite').hasClass('updated')) {
        $('#userRiskAppetite').addClass('ui-tabs-hide');
    };

//    $('.riskSummary a').click(function() {
//
      $('#userRiskAppetite').addClass('updated');
//
//      var modalRisk = $('.modalRiskAppetite');
      var initialSliderValue = Math.round(data.riskProportionLimit * 100);

//      modalRisk.dialog({
//        modal: true,
//        draggable: false,
//        resizable: false,
//        width: 446,
//        open: function(event, ui) {

          function refresh(sliderValue) {
            $('.userCurrentRiskAppetitePercentage').text("(" + sliderValue + "%)");

            var limitAsText = vidivici.renderRiskLimitAsText(sliderValue / 100);
            $('.userCurrentRiskAppetite').text(limitAsText.short);
            $('.userCurrentRiskAppetiteLong').text(limitAsText.long);

            $('#newRiskLimit').attr('value', sliderValue / 100);

            if (sliderValue == initialSliderValue) {
                $('#userRiskAppetite .save').attr('disabled', 'disabled').removeClass('prompt');
                $('#userRiskAppetite .cancel').attr('disabled', 'disabled');
            } else {
                $('#userRiskAppetite .save').removeAttr('disabled').addClass('prompt');
                $('#userRiskAppetite .cancel').removeAttr('disabled');
            }
          };

          $('.sliderRiskAppetite').slider({
            range: "min",
            value: initialSliderValue,
            min: 0,
            max: 100,
            slide: function( event, ui ) {
              refresh(ui.value);
            }
          });

          $('#userRiskAppetite a.cancel').click(function() {
//            modalRisk.dialog("destroy");
            $('.sliderRiskAppetite').slider('value', initialSliderValue);
            refresh(initialSliderValue);
            return false;
          });

          refresh(initialSliderValue);
//        },
//        close: function(event, ui){
//          $(this).dialog("destroy");
//        }
//      });
//      return false;
//    });
}

vidivici.renderRiskLimitAsText = function(limit)
{
  var r = {};
  if (limit < 0.05) {
      r.short = 'Extreme aversion to losing money';
      r.long = 'If you can make a return above bank account interest, that’s a bonus but not essential. If in doubt you’ll prefer to keep your money in the bank, and proceed with great caution (if at all) when opportunities arise.';
  }
  else if (limit < 0.10) {
      r.short = 'Very cautious';
      r.long = 'You think you can do a competent job of managing your own portfolio, but will be happy with a modest return (for example recouping what you might’ve otherwise paid in fund management fees). This is a large proportion of your total savings and you really don’t want to lose it.';
  }
  else if (limit < 0.20) {
      r.short = 'Cautious';
      r.long = 'You think you can do a competent job of managing your own portfolio, but will be happy with a modest return (for example recouping what you might’ve otherwise paid in fund management fees).';
  }
  else if (limit < 0.40) {
      r.short = 'Moderate';
      r.long = 'You’re fed up with the rates on offer elsewhere (with supposed low-risk investments) and reckon that by taking a few calculated chances you can do a lot better.';
  }
  else if (limit < 0.60) {
      r.short = 'Adventurous';
      r.long = 'You follow company news closely and reckon you have a good feel for where prices are going. You’d like to see how well you can do and are prepared to take some knocks along the way.';
  }
  else if (limit < 0.80) {
      r.short = 'Gung-ho';
      r.long = 'This is just a game for you, but you reckon your judgement’s pretty darn good and you can do just as well as any professional.';
  }
  else {
      r.short = 'Insatiable';
      r.long = 'You’re feeling lucky.';
  }
  return r;
}


vidivici.renderPortfolio = function(data, showUploadPortfolioPrompt)
{

    var portfolioHTML = "";

    if (showUploadPortfolioPrompt) {
        portfolioHTML = '<div class="notice">';
        var cashBalanceString = '£' + vidivici.util.currencyFormatted(data.cashBalance);
        portfolioHTML +=
            '<p class="vLabel vToDo">To get you going, we’ve given you a starting cash balance of ' + cashBalanceString +
            '. Upload your portfolio and we’ll tailor trade ideas around the investments you’ve made and the cash you have available (just go to “Post a transaction” at the bottom).</p></div>';
    }

    portfolioHTML += '<table class="portTable"><thead><tr><td>Holdings</td><td>Value</td><td></td></tr></thead>';

    var largestPosition = 0;
    for (var i = 0; i < data.positions.length; i++) {
        var position = data.positions[i];
        if (position.value > largestPosition) { largestPosition = position.value; }
    }
    if (data.cashBalance > largestPosition) { largestPosition = data.cashBalance; }

    for (var i = 0; i < data.positions.length; i++) {
        var position = data.positions[i];
        portfolioHTML += '<tbody><tr><td class="portfolioHoldings"><span class="portfolioHoldingsCompany"><strong>' + position.stockName + '</strong><em>(' + position.ticker + ')</em></span>';
        var sharesString = position.shares + ' share';
        if (position.shares > 1) sharesString += 's';
        var priceString = vidivici.util.priceString(position.stockPrice);
        portfolioHTML += '<span class="portfolioHoldingsShares">' + sharesString + ' worth ' + priceString + ' each</span>';
        portfolioHTML += '</td><td class="portfolioValue"><span class="portfolioValuePound">£</span> <span class="portfolioValueAmount">' + vidivici.util.currencyFormatted(position.value) + '</span></td>';
        var width = Math.round((position.value / largestPosition) * 95);
        portfolioHTML += '<td class="portfolioGraph"><div class="portfolioGraphWrapper"><span class="portfolioGraphDisplay" style="width: ' + width + '%">' + position.stockName + '</span></div></td></tr>';
    }
    var width = Math.round((data.cashBalance / largestPosition) * 95);
    portfolioHTML += '<tr><td class="portfolioHoldings"><span class="portfolioHoldingsCompany"><strong>Cash</strong></span>';
    portfolioHTML += '</td><td class="portfolioValue"><span class="portfolioValuePound">£</span> <span class="portfolioValueAmount">' + vidivici.util.currencyFormatted(data.cashBalance) + '</span></td>';
    portfolioHTML += '<td class="portfolioGraph"><div class="portfolioGraphWrapper portfolioCash"><span class="portfolioGraphDisplay" style="width: ' + width + '%">Cash</span></div></td></tr></tbody>';
    portfolioHTML += '<tfoot><tr><td></td><td class="portfolioValue"><span class="portfolioValuePound">£</span><span class="portfolioValueAmount">' + vidivici.util.currencyFormatted(data.totalValue) +'</span></td><td></td></tr></tfoot></table>';
    $('#userPortfolio div.portTableWrap').html(portfolioHTML);
}


// The object model for transactions is this:
//
//    {
//        transactionType: "cashTransfer",
//        timestamp: 136765823759,
//        amount: 1000.00,
//        description: "Initial deposit",
//        checkboxName: 76sd87f68sd6f
//    }
//
//    {
//        transactionType: "stockTransfer",
//        timestamp: 137826386283,
//        companyName: "Vodafone Group",
//        ticker: "LSE:VOD",
//        shares: 300,
//        description: "Account migrated from Broker ABC",
//        checkboxName: 76sd87f68sd6f
//    }
//
//    {
//        transactionType: "stockTrade",
//        timestamp: 137826386283,
//        companyName: "Vodafone Group",
//        ticker: "LSE:VOD",
//        shares: 300,
//        stockPrice: 1.40,
//        payment: 420.00,
//        fees: 10.00,
//        tax: 2.10,
//        checkboxName: 76sd87f68sd6f
//    }

vidivici.renderTransactions = function(data)
{
    var transactionsHTML = "";

    var largestTransactionSize = 0;
    for (var i = 0; i < data.length; i++) {
        var transaction = data[i];
        if (transaction.amount &&
            Math.abs(transaction.amount) > largestTransactionSize) { largestTransactionSize = Math.abs(transaction.amount); }
        if (transaction.payment) {
            var net = -(transaction.payment + transaction.fees + transaction.tax);
            if (Math.abs(net) > largestTransactionSize) { largestTransactionSize = Math.abs(net); }
        }
    }

    function widthAttribute(amount) {
        // 180px total width of bars
        var width = Math.round((Math.abs(amount) / largestTransactionSize) * 90.0);
        return 'style="width: ' + width + 'px';
    }

    for (var i = 0; i < data.length; i++) {
        var transaction = data[i];
        switch (transaction.transactionType) {
            case "cashTransfer":
                // TODO Show the description here - could be e.g. dividend receipt - we could be more specific
                var cashItemString = "Cash deposit";
                if (transaction.amount < 0) { cashItemString = "Cash withdrawal"; }
                transactionsHTML += '<tr><td class="tranHistStock">' + cashItemString + '</td>';

                var paymentHTML = '<td class="tranHistSell"></td>';
                if (transaction.amount < 0) { paymentHTML = '<td class="tranHistSell displayed"><div class="transactionGraph"' + widthAttribute(transaction.amount) + '">' + cashItemString + '</div></td>'; }
                transactionsHTML += paymentHTML;

                var receiptHTML = '<td class="tranHistBuy"></td>';
                if (transaction.amount > 0) { receiptHTML = '<td class="tranHistBuy displayed"><div class="transactionGraph"' + widthAttribute(transaction.amount) + '">' + cashItemString + '</div></td>'; }
                transactionsHTML += receiptHTML;

                transactionsHTML += '<td class="tranHistAmount"><span class="transactionAmount">' + vidivici.util.currencyFormatted(transaction.amount) + '</span></td>';
                transactionsHTML += '<td class="tranHistQuantity">-</td>';
                transactionsHTML += '<td class="tranHistTax">-</td>';
                transactionsHTML += '<td class="tranHistFees">-</td>';
                transactionsHTML += '<td class="tranHistNet">' + vidivici.util.currencyFormatted(transaction.amount) + '</td>';
                break;

            case "stockTransfer":
                var itemString = transaction.companyName + " (transfer)"
                transactionsHTML += '<tr><td class="tranHistStock">' + itemString + '</td>';

                var paymentHTML = '<td class="tranHistSell"></td>';
                if (transaction.shares < 0) { paymentHTML = '<td class="tranHistSell displayed"><div class="transactionGraph" style="width: 0">' + itemString + '</div></td>'; }
                transactionsHTML += paymentHTML;

                var receiptHTML = '<td class="tranHistBuy"></td>';
                if (transaction.shares > 0) { receiptHTML = '<td class="tranHistBuy displayed"><div class="transactionGraph" style="width: 0">' + itemString + '</div></td>'; }
                transactionsHTML += receiptHTML;

                transactionsHTML += '<td class="tranHistAmount"><span class="transactionAmount">-</span></td>';
                transactionsHTML += '<td class="tranHistQuantity">' + transaction.shares + '</td>';
                transactionsHTML += '<td class="tranHistTax">-</td>';
                transactionsHTML += '<td class="tranHistFees">-</td>';
                transactionsHTML += '<td class="tranHistNet">-</td>';
                break;

            default: // stockTrade
                var net = -(transaction.payment + transaction.fees + transaction.tax);

                var itemString = transaction.companyName;
                if (transaction.shares > 0) { itemString += " (purchase)"; }
                else { itemString += " (sale)"; }
                transactionsHTML += '<tr><td class="tranHistStock">' + itemString + '</td>';

                var paymentHTML = '<td class="tranHistSell"></td>';
                if (transaction.shares > 0) { paymentHTML = '<td class="tranHistSell displayed"><div class="transactionGraph"' + widthAttribute(net) + '">' + itemString + '</div></td>'; }
                transactionsHTML += paymentHTML;

                var receiptHTML = '<td class="tranHistBuy"></td>';
                if (transaction.shares < 0) { receiptHTML = '<td class="tranHistBuy displayed"><div class="transactionGraph"' + widthAttribute(net) + '">' + itemString + '</div></td>'; }
                transactionsHTML += receiptHTML;

                transactionsHTML += '<td class="tranHistAmount"><span class="transactionAmount">' + vidivici.util.currencyFormatted(-transaction.payment) + '</span></td>';
                transactionsHTML += '<td class="tranHistQuantity">' + transaction.shares + '</td>';
                transactionsHTML += '<td class="tranHistTax">' + vidivici.util.currencyFormatted(-transaction.tax) + '</td>';
                transactionsHTML += '<td class="tranHistFees">' + vidivici.util.currencyFormatted(-transaction.fees) + '</td>';
                transactionsHTML += '<td class="tranHistNet">' + vidivici.util.currencyFormatted(net) + '</td>';
        }
        // TODO Show hh:mm as well
        var dateString = $.datepicker.formatDate('d M yy', new Date(transaction.timestamp));
        transactionsHTML += '<td class="tranHistDate">' + dateString + '</td>';
        transactionsHTML += '<td class="tranHistEdit"><input type="checkbox" name="' + transaction.checkboxName + '" value="true"></td></tr>';
        // <input type="hidden" name="' + transaction.checkboxName + '" value="false">
    }
    $('.transactionEditor table.tranTable tbody').html(transactionsHTML);

    // Position and Toggle Transaction Editor
    function setTransPos () {
        var transEdPanel = $('.transactionEditor');
        var transHight = transEdPanel.outerHeight();
        transEdPanel.css({bottom: ((transHight * -1) + 42)});
    }
    if (!$('#userPortfolio').hasClass('populated')) {
        $('#userPortfolio').removeClass('ui-tabs-hide');
        setTransPos();
        $('#userPortfolio').addClass('ui-tabs-hide populated');
    } else {
        if (!$('.transactionEditor').hasClass('max')) {
            setTransPos();
        };
    }
    $('.transactionEditor .panelTopBar a.toggle').click(function() {
        var transEdPanel = $('.transactionEditor');
        var transHight = transEdPanel.outerHeight();
        if (transEdPanel.hasClass('min')) {
            transEdPanel.animate({bottom: '0'}, 300, function() {transEdPanel.removeClass('min').addClass('max');});
        } else{
            transEdPanel.animate({bottom: ((transHight * -1) + 42)}, 300, function() {transEdPanel.removeClass('max').addClass('min');});
        };
        return false;
    });


    $('.transactionEditor .tranHistEdit input').change(function() { // checkboxes
        if ($(this).attr('checked')) { ++transactionsChecked; }
        else { --transactionsChecked; }
        if (transactionsChecked > 0) {
            $('.transactionEditor .tranDel').removeAttr('disabled');
        } else {
            $('.transactionEditor .tranDel').attr('disabled', 'disabled');
        }
        return true;
    });
    vidivici.refreshScrollPanes();
}


vidivici.renderSearchMessage = function(data)
{
    function showProgressBar() { $(".tradeIdeasSearching").animate({height: "4px", opacity: 0.5}, 500); }
    function hideProgressBar() { $(".tradeIdeasSearching").animate({height: "0px", opacity: 0}, 500); }
    function showIdeasWaiting() { $(".navUserHome.ideasFound").addClass('ideasWaiting'); }
    function hideIdeasWaiting() { $(".navUserHome.ideasFound").removeClass('ideasWaiting'); }


    function renderMessage(str, tooltipHtml, link, autoPopUpTooltip) {
        var msgHtml = '<span class="wrapperTooltip">';
        msgHtml += '<a id="statusMsg" href="' + link + '">' + str + '</a>';
        msgHtml += tooltipHtml;
        msgHtml += '</span>';
        $('.ideasFound div').html(msgHtml);
        if (tooltipHtml && tooltipHtml.length > 0) {
            jQuery('abbr.timeago').timeago();
            // if (autoPopUpTooltip) $('.ideasFound .tooltipBelow').delay(2000).fadeIn(600);
            
            $('div.header span.wrapperTooltip').hover(function() {
              $("span", this).stop(true, true).fadeIn(400);
              }, function() {
                $("span", this).hide();
            });
            
        }
    }

    function renderIdeasTooltip(ideasSummary) {

        var foundTimeago = vidivici.util.timeAgoElement(new Date(ideasSummary.foundTime));
        var tradesText = "";
        if (ideasSummary.trades == 1) { tradesText = '1 trade'; }
        else { tradesText = ideasSummary.trades + ' trades'; }

        var erPercent = Math.round(ideasSummary.expectedReturn * 1000) / 10;
        var erChangePercent = Math.round(ideasSummary.expectedReturnChange * 1000) / 10;
        var er = "";
        if (ideasSummary.expectedReturnChange > 0) {
            er = 'Expected return trend <span class="expectedReturn">' + erPercent + '</span>% pa (<img src="site/im/sprite-trade-ideas-arrow-up.png"><span class="expectedReturnChange">' + erChangePercent + '</span>%) after costs';
        } else if (ideasSummary.expectedReturnChange < 0) {
            er = 'Expected return trend <span class="expectedReturn">' + erPercent + '</span>% pa (<img src="site/im/sprite-trade-ideas-arrow-down.png"><span class="expectedReturnChange">' + erChangePercent + '</span>%) after costs';
        } else if (ideasSummary.expectedReturnChange == 0) {
            er = 'Expected return trend <span class="expectedReturn">' + erPercent + '</span>% pa (no change) after costs';
        }

        // TODO Need to be more nuanced here!
        var risk = "";
        if (ideasSummary.riskProportionLimit < ideasSummary.riskProportion) {
            risk = 'Risk is <span class="riskChange">over</span> your <a href="#">specified limit</a>';
        } else {
            risk = 'Risk is <span class="riskChange">within</span> your specified limit';
        }

        var html = '<span class="tooltipBelow displaySticky"><b>Ideas found</b><ul>';
        html += '<li>' + tradesText + ' found ' + foundTimeago + '</li>';
        html += '<li>' + er + '</li>';
        html += '<li>' + risk + '</li>';
        html += '</ul>';
        html += '</span>';

        return html;
    }

    switch (data.msg) {
        case 0:
            renderMessage("Checking status...", "", "#");
            break;
        case 10:
            // TODO Populate tooltip
            renderMessage("Searching for ideas...", "", "#");
            showProgressBar();
            hideIdeasWaiting();
            break;
        case 20:
            var autoPopUp = false;
            // Tooltip pops up automatically if ideas were found within last 10 seconds
            if (Math.abs(data.ideasSummary.foundTime - new Date().getTime()) < (10 * 1000)) autoPopUp = true;
            renderMessage("Ideas found", renderIdeasTooltip(data.ideasSummary), "ideas", autoPopUp);
            hideProgressBar();
            showIdeasWaiting();
            break;
        case 21:
            // TODO Populate tooltip
            renderMessage("No ideas found", "", "#");
            hideProgressBar();
            hideIdeasWaiting();
            break;
        case 30:
            // TODO Show further information in tooltip - portfolio missing or empty (why?)
            renderMessage("Search failed", "", "#");
            hideProgressBar();
            break;
        case 31:
            // TODO Show further information in tooltip
            renderMessage("Search failed", "", "#");
            hideProgressBar();
            break;
    }
}


// This is the object model for a trade idea:
//
//    {
//        trades: [
//            {
//                stockName: "Vodafone Group",
//                ticker: "LSE:VOD",
//                shares: 300,
//                cashAmount: 8920.60,
//                costs: 15.00,
//                reasons: [
//                    {
//                        reason: "Just do it"
//                    },
//                    {
//                        reason: "You know it makes sense"
//                    }
//                ]
//            }
//        ],
//        foundTime: 1237685829,
//        expectedReturn: 2.3,
//        expectedReturnChange: 0.2,
//        riskProportion: 10.3,
//        riskProportionLimit: 8.0,
//        remainingCash: 10000.00
//    }

vidivici.renderIdeas = function(data)
{
    var foundTimeago = vidivici.util.timeAgoElement(new Date(data.foundTime));
    var tradesText = "";
    if (data.trades.length == 1) { tradesText = '<td><span class="tradesFoundNumber">1</span></td>'; }
    else { tradesText = '<td><span class="tradesFoundNumber">' + data.trades.length + '</span></td>'; }
    var overview = '<tbody><tr>' + tradesText ;

    var erPercent = Math.round(data.expectedReturn * 1000) / 10;
    var erChangePercent = Math.round(data.expectedReturnChange * 1000) / 10;
    if (data.expectedReturnChange > 0) {
        overview += '<td><span class="expectedReturn">' + erPercent + '</span>% pa (<img src="site/im/sprite-trade-ideas-arrow-up.png"><span class="expectedReturnChange">' + erChangePercent + '</span>%) after costs</td>';
    } else if (data.expectedReturnChange < 0) {
		overview += '<td><span class="expectedReturn">' + erPercent + '</span>% pa (<img src="site/im/sprite-trade-ideas-arrow-down.png"><span class="expectedReturnChange">' + erChangePercent + '</span>%) after costs</td>';
	} else if (data.expectedReturnChange == 0) {
	    overview += '<td><span class="expectedReturn">' + erPercent + '</span>% pa (no change) after costs</td>';
	}
	// overview += ' &nbsp; • &nbsp; ';

    // TODO Need to be more nuanced here!
    if (data.riskProportionLimit < data.riskProportion) {
	    overview += '<td><span class="riskChange">Over</span> your specified limit</td>';
	} else {
		overview += '<td><span class="riskChange">Within</span> your specified limit</td>';
	}
	overview += '</tr></tbody>';

    $(overview).insertAfter('table.ideasHeader thead');

    var largestIdeaSize = 0;
    for (var i = 0; i < data.trades.length; i++) {
        var ideaObj = data.trades[i];
        if (Math.abs(ideaObj.cashAmount) > largestIdeaSize) { largestIdeaSize = Math.abs(ideaObj.cashAmount); }
    }

    function widthAttribute(amount) {
        // 180px total width of bars
        var width = Math.round((Math.abs(amount) / largestIdeaSize) * 90);
        return 'style="width: ' + width + 'px"';
    }

    var ideas = "";
    for (var i = 0; i < data.trades.length; i++)
    {
        var ideaObj = data.trades[i];
        var company = '<div class="tradeDetails"><div class="stockNameWrapper"><a href="/#!/' + ideaObj.ticker + '" class="stockName">' + ideaObj.stockName + '</a><span class="ticker">(' + ideaObj.ticker + ')</span></div>';

        var amountText = "";
        if (ideaObj.cashAmount < 0) {
            amountText = 'Sell <span class="poundCharacter">£</span><span class="amount">' + vidivici.util.currencyFormatted(-ideaObj.cashAmount) + '</span>';
        } else {
            amountText = 'Buy <span class="poundCharacter">£</span><span class="amount">' + vidivici.util.currencyFormatted(ideaObj.cashAmount) + '</span>';
        }

        var amount = '<div class="amountWrapper">' + amountText + '</div>';
        var reasons = "";
        for (var r = 0; r < ideaObj.reasons.length; r++) {
            reasons += '<li>' + ideaObj.reasons[r].text + '</li>';
        }
        var reasonsOuter = '<ul class="reasons">' + reasons + '</ul></div>';
        ideas += '<li>' + company + amount + reasonsOuter + '</li>';
    }
    $('.tradeIdeasList ul').append(ideas);
    $('ul.reasons li:contains("The exact amount")').addClass('footnote');
    // vidivici.refreshScrollPanes();
}

vidivici.renderPortfolioComparison = function(data)
{
    var portfolioHTML = "";

    var largestPosition = 0;
    for (var i = 0; i < data.positions.length; i++) {
        var position = data.positions[i];
        if (position.valueBefore > largestPosition) { largestPosition = position.valueBefore; }
        if (position.valueAfter > largestPosition) { largestPosition = position.valueAfter; }
    }
    if (data.cashBalanceAfter > largestPosition) { largestPosition = data.cashBalanceAfter; }
    if (data.cashBalanceBefore > largestPosition) { largestPosition = data.cashBalanceBefore; }

    for (var i = 0; i < data.positions.length; i++) {
        var position = data.positions[i];
        portfolioHTML += '<li><div class="stockNameWrapper"><a href="/#!/' + position.ticker + '" class="stockName">' + position.stockName + '</a><span class="ticker">(' + position.ticker + ')</span></div>';
        var widthBefore = Math.round((position.valueBefore / largestPosition) * 95);
        var widthAfter = Math.round((position.valueAfter / largestPosition) * 95);
        portfolioHTML += '<div class="portfolioGraphs"><div class="portfolioGraphWrapper portfolioBefore"><span class="portfolioGraphDisplay" style="width: ' + widthBefore + '%"></span></div>';
        portfolioHTML += '<div class="portfolioGraphWrapper portfolioAfter"><span class="portfolioGraphDisplay" style="width: ' + widthAfter + '%"></span></div></div></li>';
    }

    portfolioHTML += '<li><div class="stockNameWrapper">Cash</div>';
    var widthBefore = Math.round((data.cashBalanceBefore / largestPosition) * 95);
    var widthAfter = Math.round((data.cashBalanceAfter / largestPosition) * 95);
    portfolioHTML += '<div class="portfolioGraphs"><div class="portfolioGraphWrapper portfolioBefore"><span class="portfolioGraphDisplay" style="width: ' + widthBefore + '%"></span></div>';
    portfolioHTML += '<div class="portfolioGraphWrapper portfolioCash portfolioAfter"><span class="portfolioGraphDisplay" style="width: ' + widthAfter + '%"></span></div></div></li>';

    $('.tradeIdeasPortfolio ul').append(portfolioHTML);
    vidivici.refreshScrollPanes();
}

vidivici.refreshScrollPanes = function() {
  $('.customScrollVisible, .customScrollHidden').jScrollPane({
    verticalGutter: 10
    //autoReinitialise: true
  });
  if ($('.jspScrollable').hasClass('customScrollHidden')) {
    $('.jspScrollable').parent().hover(function() {
      $('.jspVerticalBar', this).stop(true, true).animate({opacity: "1"}, 300);
    }, function() {
      $('.jspVerticalBar', this).animate({opacity: "0.3"}, 300);
    });
  };
};



vidivici.util = {};

vidivici.util.timeAgoElement = function(date)
{
    return '<abbr class="timeago" title="' + date.toISO8601String() + '">' + $.datepicker.formatDate('D, d M yy', date) + '</abbr>';
}

vidivici.util.getCookie = function (name)
{
    var i,x,y,ARRcookies=document.cookie.split(";");
    for (i=0;i<ARRcookies.length;i++)
    {
        x=ARRcookies[i].substr(0,ARRcookies[i].indexOf("="));
        y=ARRcookies[i].substr(ARRcookies[i].indexOf("=")+1);
        x=x.replace(/^\s+|\s+$/g,"");
        if (x==name)
        {
            return unescape(y);
        }
    }
    return null;
};



vidivici.util.priceString = function(price)
{
    if (price < 1) return vidivici.util.currencyFormatted(price * 100.0) + "p";
    else return "£" + vidivici.util.currencyFormatted(price);
};

/**
 * Formats to two decimal places with comma separation for 1000s
 */
vidivici.util.currencyFormatted = function (amount)
{
    var s = new String(Math.round(amount * 100) / 100);
//    var i = parseFloat(amount);
//    if(isNaN(i)) { i = 0.00; }
//    var minus = '';
//    if(i < 0) { minus = '-'; }
//    i = Math.abs(i);
//    i = parseInt((i + .00005) * 10000);
//    i = i / 10000;
//    s = new String(i);
    if(s.indexOf('.') < 0) { s += '.00'; }
    if(s.indexOf('.') == (s.length - 2)) { s += '0'; }
    //s = minus + s;
    return vidivici.util.commaFormatted(s);
};

/**
 * Formats with comma separation for 1000s
 * @param amount
 */
vidivici.util.commaFormatted = function (amount) {
    var s = new String(amount);
    var delimiter = ",";
    var a = s.split('.',2);
    var d = a[1];
    var i = parseInt(a[0]);
    if(isNaN(i)) { return ''; }
    var minus = '';
    if(i < 0) { minus = '-'; }
    i = Math.abs(i);
    var n = new String(i);
    var a = [];
    while(n.length > 3)
    {
        var nn = n.substr(n.length-3);
        a.unshift(nn);
        n = n.substr(0,n.length-3);
    }
    if(n.length > 0) { a.unshift(n); }
    n = a.join(delimiter);
    if(d && d.length > 0) { s = n + '.' + d; }
    else { s = n; }
    s = minus + s;
    return s;
};

/*
PAUL SOWDEN
http://delete.me.uk/2005/03/iso8601.html

This function takes two arguments, both optional. The first describes the format the resulting string should take, ie. how many components to include. This is an integer between 1 and 6, with the meanings listed above in the comment block. The second argument is an optional timezone offset. If it is not specified the timezone is set to UTC using the Z character. It takes the form +HH:MM or -HH:MM.

accepted values for the format [1-6]:
 1 Year:
   YYYY (eg 1997)
 2 Year and month:
   YYYY-MM (eg 1997-07)
 3 Complete date:
   YYYY-MM-DD (eg 1997-07-16)
 4 Complete date plus hours and minutes:
   YYYY-MM-DDThh:mmTZD (eg 1997-07-16T19:20+01:00)
 5 Complete date plus hours, minutes and seconds:
   YYYY-MM-DDThh:mm:ssTZD (eg 1997-07-16T19:20:30+01:00)
 6 Complete date plus hours, minutes, seconds and a decimal
   fraction of a second
   YYYY-MM-DDThh:mm:ss.sTZD (eg 1997-07-16T19:20:30.45+01:00)
*/
Date.prototype.toISO8601String = function (format, offset) {
    if (!format) { var format = 6; }
    if (!offset) {
        var offset = 'Z';
        var date = this;
    } else {
        var d = offset.match(/([-+])([0-9]{2}):?([0-9]{2})/);
        var offsetnum = (Number(d[2]) * 60) + Number(d[3]);
        offsetnum *= ((d[1] == '-') ? -1 : 1);
        var date = new Date(Number(Number(this) + (offsetnum * 60000)));
    }

    var zeropad = function (num) { return ((num < 10) ? '0' : '') + num; };

    var str = "";
    str += date.getUTCFullYear();
    if (format > 1) { str += "-" + zeropad(date.getUTCMonth() + 1); }
    if (format > 2) { str += "-" + zeropad(date.getUTCDate()); }
    if (format > 3) {
        str += "T" + zeropad(date.getUTCHours()) +
               ":" + zeropad(date.getUTCMinutes());
    }
    if (format > 5) {
        var secs = Number(date.getUTCSeconds() + "." +
                   ((date.getUTCMilliseconds() < 100) ? '0' : '') +
                   zeropad(date.getUTCMilliseconds()));
        str += ":" + zeropad(secs);
    } else if (format > 4) { str += ":" + zeropad(date.getUTCSeconds()); }

    if (format > 3) { str += offset; }
    return str;
};

/*
PAUL SOWDEN
http://delete.me.uk/2005/03/iso8601.html

To use it you'll first have to create a Date object and then invoke the method. The usage mirrors the setTime method provided by the standard JavaScript Date object.

*/
Date.prototype.setISO8601 = function (string) {
     var regexp = "([0-9]{4})(-([0-9]{2})(-([0-9]{2})" +
         "(T([0-9]{2}):([0-9]{2})(:([0-9]{2})(\.([0-9]+))?)?" +
         "(Z|(([-+])([0-9]{2}):?([0-9]{2})))?)?)?)?";
     var d = string.match(new RegExp(regexp));

     var offset = 0;
     var date = new Date(d[1], 0, 1);

     if (d[3]) { date.setMonth(d[3] - 1); }
     if (d[5]) { date.setDate(d[5]); }
     if (d[7]) { date.setHours(d[7]); }
     if (d[8]) { date.setMinutes(d[8]); }
     if (d[10]) { date.setSeconds(d[10]); }
     if (d[12]) { date.setMilliseconds(Number("0." + d[12]) * 1000); }
     if (d[14]) {
         offset = (Number(d[16]) * 60) + Number(d[17]);
         offset *= ((d[15] == '-') ? 1 : -1);
     }

     offset -= date.getTimezoneOffset();
     time = (Number(date) + (offset * 60 * 1000));
     this.setTime(Number(time));
};

