﻿// ==ClosureCompiler==
// @output_file_name marginCalculator.min.js
// ==/ClosureCompiler==
(function ($) {
  function isTouchDevice() {
    try {
      document.createEvent("TouchEvent");
      return true;
    } catch (e) {
      return false;
    }
  }

  var
    /** @const */ SEL_BUTTON_APPLY_BATCH_MARGIN = '.applyBatchMargin button',
    /** @const */ SEL_INPUT_BATCH_MARGIN_VALUE = '.batchMarginValue input',
    /** @const */ SEL_INPUT_BATCH_MARGIN_PERCENT = '.batchMarginPercent input',

    /** @const */ SEL_INPUT_PRODUCT_ID = 'input.productId',

    /** @const */ SEL_INPUT_PURCHASE_PRICE = '.purchasePrice input',
    /** @const */ SEL_INPUT_MARGIN_VALUE = '.marginValue input',
    /** @const */ SEL_INPUT_MARGIN_PERCENT = '.marginPercent input',
    /** @const */ SEL_INPUT_VAT_PERCENT = 'input.vatPercent',
    /** @const */ SEL_INPUT_QTY_PER_PACKAGE = 'input.qtyPerPackage',
    /** @const */ SEL_INPUT_RETAIL_PRICE = '.retailPrice input',
    /** @const */ SEL_VAL_VAT = '.vat span:first',

    /** @const */ SEL_INPUT_MONTH_QTY = '.monthQuantity input',
    /** @const */ SEL_INPUT_PROFIT = '.profit input',

    /** @const */ CLASS_AUTO = 'auto';
    /** @const */ CLASS_CURRENT = 'current',

    _isTouchDevice = isTouchDevice();

  function _parseFloat(val, def) {
    def = def || 0;
    var f = parseFloat(('' + val).replace(',', '.').replace(/\s+/g, ''));
    return isNaN(f) ? def : f;
  }
  function _parseInt(val, def) {
    def = def || 0;
    var i = parseInt(('' + val).replace(/\s+/g, ''), 10);
    return isNaN(i) ? def : i;
  }

  function formatNumber(val, precision) {
    precision = precision || 0;
    return $().number_format(_parseFloat(val), { numberOfDecimals: precision, decimalSeparator: ',', thousandSeparator: ' ' });
  }

  function getLinkedRow($row) {
    if ($row.hasClass('roll')) {
      return $row.parent().find('tr.can:has(' + SEL_INPUT_PRODUCT_ID + '[value="' + $row.find(SEL_INPUT_PRODUCT_ID).val() + '"])');
    } else {
      return $row.parent().find('tr.roll:has(' + SEL_INPUT_PRODUCT_ID + '[value="' + $row.find(SEL_INPUT_PRODUCT_ID).val() + '"])');
    }
  }


  /*************************************************
  *  Save a CalculatorProduct for the next time the user visits the page.
  *************************************************/
  function postItem($row) {
    var loc = document.location,
        url = loc.protocol + '//' + loc.host + loc.pathname + '?q=updateCalcProduct',
        $canRow, $rollRow,
        product = {};

    if ($row.hasClass('roll')) {
      $canRow = getLinkedRow($row);
      $rollRow = $row;
    } else {
      $canRow = $row;
      $rollRow = getLinkedRow($row);
    }

    product['ProductId'] = $row.find(SEL_INPUT_PRODUCT_ID).val();
    product['PurchasePrice'] = _parseFloat($row.find(SEL_INPUT_PURCHASE_PRICE).val());

    product['RollMargin'] = _parseFloat($rollRow.find(SEL_INPUT_MARGIN_VALUE).val());
    product['CanMargin'] = _parseFloat($canRow.find(SEL_INPUT_MARGIN_VALUE).val());

    product['RollMonthQuantity'] = _parseFloat($rollRow.find(SEL_INPUT_MONTH_QTY).val());
    product['RollProfit'] = _parseFloat($rollRow.find(SEL_INPUT_PROFIT).val());

    product['CanMonthQuantity'] = _parseFloat($canRow.find(SEL_INPUT_MONTH_QTY).val());
    product['CanProfit'] = _parseFloat($canRow.find(SEL_INPUT_PROFIT).val());

    product['AutoPurchasePrice'] = $row.find(SEL_INPUT_PURCHASE_PRICE).hasClass(CLASS_AUTO);
    product['AutoMarginValue'] = $row.find(SEL_INPUT_MARGIN_VALUE).hasClass(CLASS_AUTO);
    product['AutoMarginPercent'] = $row.find(SEL_INPUT_MARGIN_PERCENT).hasClass(CLASS_AUTO);
    product['AutoRetailPrice'] = $row.find(SEL_INPUT_RETAIL_PRICE).hasClass(CLASS_AUTO);

    product['AutoMonthQuantity'] = $row.find(SEL_INPUT_MONTH_QTY).hasClass(CLASS_AUTO);
    product['AutoProfit'] = $row.find(SEL_INPUT_PROFIT).hasClass(CLASS_AUTO);

    $.post(url, {
      target: product.ProductId,
      item: $.toJSON(product)
    });
    return false;
  }

  /*************************************************
  *  Called when the purchase price was modified.
  *************************************************/
  function onChangePurchasePrice(e) {
    var $this = $(this),
        $row = $this.closest('tr'),
        $marginValue = $row.find(SEL_INPUT_MARGIN_VALUE),
        $marginPercent = $row.find(SEL_INPUT_MARGIN_PERCENT),
        $retailPrice = $row.find(SEL_INPUT_RETAIL_PRICE),

        canMode = $this.hasClass('can'),
        vatPercent = _parseFloat($row.find(SEL_INPUT_VAT_PERCENT).val()),
        qtyPerPackage = _parseFloat($row.find(SEL_INPUT_QTY_PER_PACKAGE).val(), 1),
        purchasePrice = _parseFloat(this.value),
        marginValue,
        marginPercent,
        marginPrice,
        vat,
        retailPrice;

    if (isNaN(purchasePrice) || !purchasePrice) {
      return false;
    }

    if (e) {
      var $target = getLinkedRow($row).find(SEL_INPUT_PURCHASE_PRICE);
      $target.val(formatNumber(_parseFloat(this.value), 2))
      onChangePurchasePrice.call($target.get(0), null);
    }

    purchasePrice /= qtyPerPackage;

    if ($retailPrice.hasClass(CLASS_AUTO)) {
      // The Retail price is dynamic, which implies that one of the margin values is fixed.

      // Check which margin is dynamic and calculate it from the purchase price.
      if ($marginValue.hasClass(CLASS_AUTO)) {
        marginPercent = _parseFloat($marginPercent.val());
        marginValue = purchasePrice * (marginPercent / 100);
        $marginPercent.removeClass(CLASS_AUTO);
        $marginValue.addClass(CLASS_AUTO).val(formatNumber(marginValue, 2));
      } else {
        marginValue = _parseFloat($marginValue.val(), 0);
        marginPercent = (marginValue / purchasePrice) * 100;
        $marginValue.removeClass(CLASS_AUTO);
        $marginPercent.addClass(CLASS_AUTO).val(formatNumber(marginPercent, 2));
      }

      marginPrice = purchasePrice + marginValue;
      vat = marginPrice * vatPercent;
      retailPrice = purchasePrice + marginValue + vat;

      $retailPrice.val(formatNumber(retailPrice, 2));
    } else {
      // The Retail price is fixed, so both margins have to be dynamic.

      retailPrice = _parseFloat($retailPrice.val(), purchasePrice);

      marginPrice = retailPrice / (1 + vatPercent);
      vat = marginPrice * vatPercent;

      marginValue = marginPrice - purchasePrice;
      marginPercent = (marginValue / purchasePrice) * 100;

      $marginValue.addClass(CLASS_AUTO).val(formatNumber(marginValue, 2));
      $marginPercent.addClass(CLASS_AUTO).val(formatNumber(marginPercent, 2));
    }

    $row.find(SEL_VAL_VAT).text(formatNumber(vat, 2));

    if (e) {
      updateProfit($row);
      postItem($row);
    }
    return false;
  }

  /*************************************************
  *  Called when one of the margin literal value is modified.
  *************************************************/
  function onChangeMargin(e) {
    var $this = $(this),
        $row = $this.closest('tr'),
        $purchasePrice = $row.find(SEL_INPUT_PURCHASE_PRICE),
        $retailPrice = $row.find(SEL_INPUT_RETAIL_PRICE),

        vatPercent = _parseFloat($row.find(SEL_INPUT_VAT_PERCENT).val()),
        qtyPerPackage = _parseFloat($row.find(SEL_INPUT_QTY_PER_PACKAGE).val(), 1),
        purchasePrice = _parseFloat($purchasePrice.val()),
        marginValue = _parseFloat(this.value),
        marginPercent,
        vat,
        retailPrice;

    if (isNaN(marginValue) || isNaN(purchasePrice) || !purchasePrice) {
      return false;
    }

    if (e) {
      onChangeMargin.call(getLinkedRow($row).find(SEL_INPUT_MARGIN_VALUE).get(0), null);
    }

    purchasePrice /= qtyPerPackage;
    vat = (purchasePrice + marginValue) * vatPercent;
    retailPrice = purchasePrice + marginValue + vat;
    marginPercent = (marginValue / purchasePrice) * 100;

    $this.removeClass(CLASS_AUTO);
    $row.find(SEL_INPUT_MARGIN_PERCENT).addClass(CLASS_AUTO).val(formatNumber(marginPercent, 2));
    $row.find(SEL_INPUT_RETAIL_PRICE).addClass(CLASS_AUTO).val(formatNumber(retailPrice, 2));
    $row.find(SEL_VAL_VAT).text(formatNumber(vat, 2));

    if (e) {
      updateProfit($row);
      postItem($row);
    }
    return false;
  }

  /*************************************************
  *  Called when the margin percent value is modified.
  *************************************************/
  function onChangeMarginPercent(e) {
    var $this = $(this),
        $row = $this.closest('tr'),
        $purchasePrice = $row.find(SEL_INPUT_PURCHASE_PRICE),
        $retailPrice = $row.find(SEL_INPUT_RETAIL_PRICE),

        vatPercent = _parseFloat($row.find(SEL_INPUT_VAT_PERCENT).val()),
        qtyPerPackage = _parseFloat($row.find(SEL_INPUT_QTY_PER_PACKAGE).val(), 1),
        marginPercent = _parseFloat(this.value) / 100,
        purchasePrice = _parseFloat($purchasePrice.val()),
        marginValue,
        retailPrice,
        vat;

    if (isNaN(marginPercent) || isNaN(purchasePrice)) {
      return false;
    }

    if (e) {
      onChangeMarginPercent.call(getLinkedRow($row).find(SEL_INPUT_MARGIN_PERCENT).get(0), null);
    }

    purchasePrice /= qtyPerPackage;
    marginValue = purchasePrice * marginPercent;
    vat = (purchasePrice + marginValue) * vatPercent;
    retailPrice = purchasePrice + marginValue + vat;

    $this.removeClass(CLASS_AUTO);
    $row.find(SEL_INPUT_MARGIN_VALUE).addClass(CLASS_AUTO).val(formatNumber(marginValue, 2));
    $row.find(SEL_INPUT_RETAIL_PRICE).addClass(CLASS_AUTO).val(formatNumber(retailPrice, 2));
    $row.find(SEL_VAL_VAT).text(formatNumber(vat, 2));

    if (e) {
      updateProfit($row);
      postItem($row);
    }
    return false;
  }

  /*************************************************
  *  Called when the Retail price is modified.
  *************************************************/
  function onChangeRetailPrice(e) {
    var $this = $(this),
        $row = $this.closest('tr'),
        $marginValue = $row.find(SEL_INPUT_MARGIN_VALUE),
        $marginPercent = $row.find(SEL_INPUT_MARGIN_PERCENT),
        $purchasePrice = $row.find(SEL_INPUT_PURCHASE_PRICE),
        vatPercent = _parseFloat($row.find(SEL_INPUT_VAT_PERCENT).val()),
        qtyPerPackage = _parseFloat($row.find(SEL_INPUT_QTY_PER_PACKAGE).val(), 1),
        purchasePrice = _parseFloat($purchasePrice.val()),
        retailPrice = _parseFloat(this.value),
        marginPrice,
        vat,
        marginValue,
        marginPercent;

    if (isNaN(retailPrice) || isNaN(purchasePrice) || !purchasePrice) {
      return false;
    }

    if (e) {
      onChangeMarginPercent.call(getLinkedRow($row).find(SEL_INPUT_RETAIL_PRICE).get(0), null);
    }

    purchasePrice /= qtyPerPackage;
    marginPrice = retailPrice / (1 + vatPercent);
    vat = marginPrice * vatPercent;
    marginValue = marginPrice - purchasePrice;
    marginPercent = (marginValue / purchasePrice) * 100;

    $this.removeClass(CLASS_AUTO);
    $marginValue.addClass(CLASS_AUTO).val(formatNumber(marginValue, 2));
    $marginPercent.addClass(CLASS_AUTO).val(formatNumber(marginPercent, 2));
    $row.find(SEL_VAL_VAT).text(formatNumber(vat, 2));

    if (e) {
      updateProfit($row);
      postItem($row);
    }
    return false;
  }

  /*************************************************
  *  Called when the month quantity is modified.
  *************************************************/
  function onChangeMonthQuantity(e) {
    var $this = $(this),
        $row = $this.closest('tr'),
        $profit = $row.find(SEL_INPUT_PROFIT),
        marginValue = _parseFloat($row.find(SEL_INPUT_MARGIN_VALUE).val()),
        monthQuantity = _parseInt(this.value);

    if (isNaN(monthQuantity) || isNaN(marginValue)) {
      return false;
    }

    if (e) {
      onChangeMonthQuantity.call(getLinkedRow($row).find(SEL_INPUT_MONTH_QTY).get(0), null);
    }

    $this.removeClass(CLASS_AUTO);
    $profit.addClass(CLASS_AUTO).val(formatNumber(marginValue * monthQuantity * 12, 0));

    if (e) {
      postItem($row);
    }
    return false;
  }
  /*************************************************
  *  Called when the year profit is modified.
  *************************************************/
  function onChangeProfit(e) {
    var $this = $(this),
        $row = $this.closest('tr'),
        $monthQuantity = $row.find(SEL_INPUT_MONTH_QTY),
        marginValue = _parseFloat($row.find(SEL_INPUT_MARGIN_VALUE).val()),
        profit = _parseFloat(this.value);

    if (isNaN(profit) || isNaN(marginValue)) {
      return false;
    }

    if (e) {
      onChangeProfit.call(getLinkedRow($row).find(SEL_INPUT_PROFIT).get(0), null);
    }

    $this.removeClass(CLASS_AUTO);
    $monthQuantity.addClass(CLASS_AUTO).val(formatNumber(marginValue ? Math.ceil(profit / marginValue / 12) : 0, 0));

    if (e) {
      postItem($row);
    }
    return false;
  }

  /*************************************************
  *  Update the profit/month quantity from the margin values.
  *************************************************/
  function updateProfit($row) {
    var $monthQuantity = $row.find(SEL_INPUT_MONTH_QTY),
        $profit = $row.find(SEL_INPUT_PROFIT),
        marginValue = _parseFloat($row.find(SEL_INPUT_MARGIN_VALUE).val()),
        monthQuantity = _parseInt($monthQuantity.val());
        profit = _parseFloat($profit.val());

    if ($monthQuantity.hasClass(CLASS_AUTO)) {
      $monthQuantity.val(formatNumber(Math.ceil(marginValue ? profit / marginValue / 12 : 0)), 0);
    } else {
      $profit.val(formatNumber(marginValue * monthQuantity * 12, 0));
    }
  }

  /*************************************************
  *  Called when a batch input value is modified.
  *************************************************/
  function onChangeBatchInput(e) {
    var $this = $(this),
        $row = $this.closest('tr'),
        $applyButton = $row.find(SEL_BUTTON_APPLY_BATCH_MARGIN),
        $sibling = $this.parent().siblings('.batchMarginValue,.batchMarginPercent').children('input');

    if ($this.val()) {
      $this.addClass(CLASS_CURRENT);
      $sibling.removeClass(CLASS_CURRENT);
      $applyButton.removeAttr('disabled');
      return true;
    }
    if ($sibling.val()) {
      $sibling.addClass(CLASS_CURRENT);
      $this.removeClass(CLASS_CURRENT);
      $applyButton.removeAttr('disabled');
      return true;
    }

    $this.removeClass(CLASS_CURRENT);
    $sibling.removeClass(CLASS_CURRENT);
    $applyButton.attr('disabled', 'disabled');

    return false;
  }
  /*************************************************
  *  Set the values for all the visible rows.
  *************************************************/
  function applyBatchMargin() {
    var $this = $(this),
        $row = $this.closest('tr'),
        $curr = $row.find('.' + CLASS_CURRENT),
        $table = $row.parent(),
        $targets = $table.find('.' + $curr.attr('name') + ':visible input'),
        val = formatNumber($curr.val(), 2);

    $row.find(SEL_BUTTON_APPLY_BATCH_MARGIN).attr('disabled', 'disabled');
    $row.find(SEL_INPUT_BATCH_MARGIN_VALUE).removeClass(CLASS_CURRENT).val('');
    $row.find(SEL_INPUT_BATCH_MARGIN_PERCENT).removeClass(CLASS_CURRENT).val('');
    $targets.each(function() {
      $(this).addClass(CLASS_CURRENT);
    });
    $targets.each(function() {
      $(this).val(val).change();
    });

    setTimeout(function() {$table.find('.' + CLASS_CURRENT).removeClass(CLASS_CURRENT);}, 1500);

    return false;
  }

  /*************************************************
  *  Initialize the margin calculator.
  *************************************************/
  function MarginCalculator(target, controller) {
    target = $(target);
    controller = $(controller);

    target.find('.roll').hide();

    //TODO: add 'numeric' to closure export list.
    target.find('input.intInput')
      .numeric(',')
      .blur(function () {
        this.value = formatNumber(_parseInt(this.value));
      });
    target.find('input.floatInput')
      .numeric(',')
      .blur(function () {
        this.value = formatNumber(_parseFloat(this.value), 2);
      });

    target.find(SEL_INPUT_BATCH_MARGIN_VALUE)
      .numeric(',')
      .blur(function () {
        var val = _parseFloat(this.value, -1);
        this.value = val < 0 ? '' : formatNumber(val, 2);
      });
    target.find(SEL_INPUT_BATCH_MARGIN_PERCENT)
      .numeric(',')
      .blur(function () {
        var val = _parseFloat(this.value, -1);
        this.value = val < 0 ? '' : formatNumber(val, 2);
      });

    function onKeypress(callback, e) {
      var $this = null,
          $row = null;
      if (e.which === 38 || e.which === 40) {
        $this = $(this);
        if ($this.data('__prevVal') != this.value) {
          $this.change();
        }
        $row = e.which === 38
          ? $this.closest('tr').prevAll('tr:visible:first')
          : $this.closest('tr').nextAll('tr:visible:first');
        $row.find('.' + $this.parent().attr('class') + ' input').focus().select();
        return true;
      }
      if (e.which !== 13) {
        return true;
      }

      $(this).blur().focus();
      return callback.call(this, e);
    }
    function onChange(callback, e) {
      var $this = $(this);
      if ($this.data('__prevVal') == this.value) { return true; }
      return callback.call(this, e);
    }
    function onBatchKeyPress(e) {
      return e.which === 13 ? applyBatchMargin.call(this) : true;
    }

    target.find('input').focus(function() {
      $(this).data('__prevVal', this.value);
      if (_isTouchDevice) {
        this.select();
      }
    });

    target.find(SEL_INPUT_PURCHASE_PRICE)
      .change(function(e){return onChange.call(this, onChangePurchasePrice, e);})
      .keydown(function (e) { return onKeypress.call(this, onChangePurchasePrice, e); });
    target.find(SEL_INPUT_MARGIN_VALUE)
      .change(function(e){return onChange.call(this, onChangeMargin, e);})
      .keydown(function (e) { return onKeypress.call(this, onChangeMargin, e); });
    target.find(SEL_INPUT_MARGIN_PERCENT)
      .change(function(e){return onChange.call(this, onChangeMarginPercent, e);})
      .keydown(function (e) { return onKeypress.call(this, onChangeMarginPercent, e); });
    target.find(SEL_INPUT_RETAIL_PRICE)
      .change(function(e){return onChange.call(this, onChangeRetailPrice, e);})
      .keydown(function (e) { return onKeypress.call(this, onChangeRetailPrice, e); });

    target.find(SEL_INPUT_MONTH_QTY)
      .change(function(e){return onChange.call(this, onChangeMonthQuantity, e);})
      .keydown(function (e) { return onKeypress.call(this, onChangeMonthQuantity, e); });
    target.find(SEL_INPUT_PROFIT)
      .change(function(e){return onChange.call(this, onChangeProfit, e);})
      .keydown(function(e){return onKeypress.call(this, onChangeProfit, e);});

    target.find(SEL_BUTTON_APPLY_BATCH_MARGIN).click(applyBatchMargin);
    target.find(SEL_INPUT_BATCH_MARGIN_VALUE)
      .change(function(e){return onChange.call(this, onChangeBatchInput, e);})
      .keyup(onChangeBatchInput)
      .keypress(onBatchKeyPress);
    target.find(SEL_INPUT_BATCH_MARGIN_PERCENT)
      .change(function(e){return onChange.call(this, onChangeBatchInput, e);})
      .keyup(onChangeBatchInput)
      .keypress(onBatchKeyPress);

    if (controller) {
      controller.find('p.modeSelector a[rel="canMode"]').click(function () {
        controller.find('p.modeSelector').removeClass('active');
        $(this).parent().addClass('active');
        target.find('.roll').hide();
        target.find('.can').show();
        return false;
      });
      controller.find('p.modeSelector a[rel="rollMode"]').click(function () {
        controller.find('p.modeSelector').removeClass('active');
        $(this).parent().addClass('active');
        target.find('.can').hide();
        target.find('.roll').show();
        return false;
      });
      controller.find('.selectProducts').click(function () {
        window.location = this.value;
        return false;
      });
    }
  }

  window["MarginCalculator"] = MarginCalculator;
})(jQuery);
