/*
 * Axelor Business Solutions
 *
 * Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
 *
 * This program is free software: you can redistribute it and/or  modify
 * it under the terms of the GNU Affero General Public License, version 3,
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
(function() {

"use strict";

var ui = angular.module('axelor.ui');

var regional = {
  monthNames: [
    _t('January'),
    _t('February'),
    _t('March'),
    _t('April'),
    _t('May'),
    _t('June'),
    _t('July'),
    _t('August'),
    _t('September'),
    _t('October'),
    _t('November'),
    _t('December')],

  monthNamesShort: [
    _t('Jan'),
    _t('Feb'),
    _t('Mar'),
    _t('Apr'),
    _t('May'),
    _t('Jun'),
    _t('Jul'),
    _t('Aug'),
    _t('Sep'),
    _t('Oct'),
    _t('Nov'),
    _t('Dec')],

  dayNames: [
    _t('Sunday'),
    _t('Monday'),
    _t('Tuesday'),
    _t('Wednesday'),
    _t('Thursday'),
    _t('Friday'),
    _t('Saturday')],

  dayNamesShort :	[_t('Sun'), _t('Mon'), _t('Tue'), _t('Wed'), _t('Thu'), _t('Fri'), _t('Sat')],
   dayNamesMin :	[_t('Su'), _t('Mo'), _t('Tu'), _t('We'), _t('Th'), _t('Fr'), _t('Sa')],
  weekHeader : 	_t('Wk'),
  hourText : 		_t('Hour'),
  minuteText : 	_t('Minute'),
  secondText : 	_t('Second'),
  currentText : 	_t('Now'),
  closeText :		_t('Done'),
  prevText : 		_t('Prev'),
  nextText : 		_t('Next'),
  firstDay: 		1
};

// configure datepicker
$.timepicker.setDefaults(regional);
$.datepicker.setDefaults(regional);

// configure moment.js
moment.locale('en', {
  months: regional.monthNames,
  monthsShort: regional.monthNamesShort,
  weekdays: regional.dayNames,
  weekdaysShort: regional.dayNamesShort,
  weekdaysMin: regional.dayNamesMin,
  calendar : {
        sameDay : _t('[Today at] LT'),
        nextDay : _t('[Tomorrow at] LT'),
        nextWeek : _t('dddd [at] LT'),
        lastDay : _t('[Yesterday at] LT'),
        lastWeek : _t('[Last] dddd [at] LT'),
        sameElse : _t('L')
    },
  relativeTime : {
        future : _t("in %s"),
        past : _t("%s ago"),
        s : _t("a few seconds"),
        m : _t("a minute"),
        mm : _t("%d minutes"),
        h : _t("an hour"),
        hh : _t("%d hours"),
        d : _t("a day"),
        dd : _t("%d days"),
        M : _t("a month"),
        MM : _t("%d months"),
        y : _t("a year"),
        yy : _t("%d years")
    }
});

// configure ui.mask
function createTwoDigitDefinition( maximum ) {
  return function( value ) {
    var number = parseInt( value, 10 );

    if (value === "" || /\D/.test(value) || _.isNaN(number)) {
      return false;
    }

    // pad to 2 characters
    number = ( number < 10 ? "0" : "" ) + number;
    if ( number <= maximum ) {
      return number;
    }
  };
}

function yearsDefinition( value ) {
  var temp;

  // if the value is empty, or contains a non-digit, it is invalid
  if ( value === "" || /\D/.test( value ) ) {
    return false;
  }

  temp = parseInt( value, 10 );

  // convert 2 digit years to 4 digits, this allows us to type 80 <right>
  if ( value.length <= 2 ) {
    // before "32" we assume 2000's otherwise 1900's
    if ( temp < 10 ) {
      return "200" + temp;
    } else if ( temp < 32 ) {
      return "20" + temp;
    } else {
      return "19" + temp;
    }
  }
  if ( temp === 0 ) {
    return "2000";
  }
  if ( value.length === 3 ) {
    return "0"+value;
  }
  if ( value.length === 4 ) {
    return value;
  }
}

$.extend($.ui.mask.prototype.options.definitions, {
  "MM": createTwoDigitDefinition( 12 ),
  "DD": createTwoDigitDefinition( 31 ),
  "YYYY": yearsDefinition,
  "HH": createTwoDigitDefinition( 23 ),
  "mm": createTwoDigitDefinition( 59 )
});

// datepicker keyboad navigation hack
var _doKeyDown = $.datepicker._doKeyDown;
$.extend($.datepicker, {
  _doKeyDown: function(event) {
    var inst = $.datepicker._getInst(event.target),
      handled = false;
    inst._keyEvent = true;
    if ($.datepicker._datepickerShowing) {
      switch (event.keyCode) {
      case 36: // home
        $.datepicker._gotoToday(event.target);
        handled = true;
        break;
      case 37: // left
        $.datepicker._adjustDate(event.target, -1, "D");
        handled = true;
        break;
      case 38: // up
        $.datepicker._adjustDate(event.target, -7, "D");
        handled = true;
        break;
      case 39: // right
        $.datepicker._adjustDate(event.target, +1, "D");
        handled = true;
        break;
      case 40: // down
        $.datepicker._adjustDate(event.target, +7, "D");
        handled = true;
        break;
      }
      if (handled) {
        event.ctrlKey = true;
      }
    } else if (event.keyCode === 36 && event.ctrlKey) { // display the date picker on ctrl+home
      $.datepicker._showDatepicker(this);
      handled = true;
    }
    if (handled) {
      event.preventDefault();
      event.stopPropagation();
    } else {
      _doKeyDown(event);
    }
  }
});

var _updateDatepicker = $.datepicker._updateDatepicker;
$.datepicker._updateDatepicker = function(inst) {
  if (!$.datepicker._noUpdate) {
    return _updateDatepicker.call($.datepicker, inst);
  }
};

var _parseDate = $.datepicker.parseDate;
$.datepicker.parseDate = function (format, value, settings) {
  if (_.isString(value) && value.indexOf('_') > -1) {
    return;
  }
  return _parseDate.call($.datepicker, format, value, settings);
};

// suppress annoying logs
$.timepicker.log = function () {};

/**
 * The DateTime input widget.
 */
ui.formInput('DateTime', {

  css	: 'datetime-item',

  format: 'DD/MM/YYYY HH:mm',
  mask: 'DD/MM/YYYY HH:mm',

  widgets: ['Datetime'],

  init: function(scope) {

    var isDate = this.isDate,
      format = this.format;

    scope.parse = function(value) {
      if (angular.isDate(value)) {
        return isDate ? moment(value).startOf('day').format('YYYY-MM-DD') : value.toISOString();
      }
      return value;
    };

    scope.format = function(value) {
      if (value) {
        return moment(value).format(format);
      }
      return value;
    };
  },

  link_editable: function(scope, element, attrs, model) {
    var input = element.children('input:first');
    var button = element.find('i:first');
    var onChange = scope.$events.onChange;
    var props = scope.field;
    var isDate = this.isDate;
    var isShowing = false;
    var lastValue = null;

    var options = {
      dateFormat: 'dd/mm/yy',
      showButtonsPanel: false,
      showTime: false,
      showOn: null,
      beforeShow: function (e, ui) {
        lastValue = input.mask("value") || '';
        isShowing = true;
      },
      onClose: function (e, ui) {
        lastValue = null;
        isShowing = false;
      },
      onSelect: function(dateText, inst) {
        input.mask('value', dateText);
        updateModel();
        if (!inst.timeDefined) {
          input.datetimepicker('hide');
          setTimeout(function(){
            input.focus().select();
          });
        }
      }
    };

    if (this.isDate) {
      options.showTimepicker = false;
    }

    input.datetimepicker(options);
    input.mask({
      mask: this.mask
    });

    var changed = false;
    var rendering = false;

    input.on('change', function(e, ui){
      if (changed) return;
      if (isShowing) {
        changed = lastValue !== (input.mask("value") || '');
      } else {
        changed = !rendering;
      }
    });
    input.on('blur', function() {
      if (changed) {
        changed = false;
        updateModel();
      }
    });
    input.on('grid:check', function () {
      updateModel();
    });
    input.on('keydown', function(e){

      if (e.isDefaultPrevented()) {
        return;
      }

      if (e.keyCode === $.ui.keyCode.DOWN) {
        input.datetimepicker('show');
        e.stopPropagation();
        e.preventDefault();
        return false;
      }
      if (e.keyCode === $.ui.keyCode.ENTER && $(this).datepicker("widget").is(':visible')) {
        e.stopPropagation();
        e.preventDefault();
        return false;
      }
      if (e.keyCode === $.ui.keyCode.ENTER || e.keyCode === $.ui.keyCode.TAB) {
        if (changed) updateModel();
      }
    });
    button.click(function(e, ui){
      if (scope.isReadonly()) {
        return;
      }
      var mobile = axelor.device.mobile;
      if (mobile) {
        input.attr('disabled', 'disabled')
           .addClass('mobile-disabled-input');
      }
      input.datetimepicker('show');
      if (mobile) {
        input.removeAttr('disabled')
           .removeClass('mobile-disabled-input');
      }
    });

    scope.$onAdjust('size scroll', function () {
      if (isShowing) {
        input.datepicker('widget').hide();
        input.datetimepicker('hide');
      }
    });

    function updateModel() {
      var masked = input.mask("value") || '',
        value = input.datetimepicker('getDate') || null,
        oldValue = scope.getValue() || null;

      if (_.isEmpty(masked)) {
        value = null;
      }
      if (!input.mask("valid")) {
        model.$setViewValue(value); // force validation
        model.$render();
        scope.$applyAsync();
        return;
      }

      value = scope.parse(value);

      if (angular.equals(value, oldValue)) {
        return;
      }

      scope.setValue(value, true);
      scope.$applyAsync();
    }

    scope.validate = function(value) {
      var minSize = props.minSize === 'now' ? moment() : props.minSize,
        maxSize = props.maxSize,
        val = moment(value),
        valid = true;

      var masked = input.mask('value');
      if (masked && !input.mask("valid")) {
        return false;
      }

      if(isDate) {
        if(minSize) minSize = moment(minSize).startOf('day');
        if(maxSize) maxSize = moment(maxSize).startOf('day');
      }
      else {
        if(minSize) minSize = moment(minSize);
        if(maxSize) maxSize = moment(maxSize);
      }

      if(minSize) {
        if(!val) return false;
        valid = !val.isBefore(minSize) ;
      }
      if(valid && maxSize) {
        if(!val) return true;
        valid = !val.isAfter(maxSize) ;
      }

      return valid;
    };

    scope.$render_editable = function() {
      rendering = true;
      try {
        var value = scope.getText();
        if (value) {
          input.mask('value', value);
          try {
            $.datepicker._noUpdate = true;
            $.datepicker._setDateDatepicker(input[0], moment(scope.getValue()).toDate());
            input.mask('refresh');
          } finally {
            $.datepicker._noUpdate = false;
          }
        } else {
          input.mask('value', '');
        }
      } finally {
        rendering = false;
      }
    };
  },
  template_editable:
  '<span class="picker-input">'+
    '<input type="text" autocomplete="off">'+
    '<span class="picker-icons">'+
      '<i class="fa fa-calendar"></i>'+
    '</span>'+
  '</span>'
});

ui.formInput('Date', 'DateTime', {
  format: 'DD/MM/YYYY',
  mask: 'DD/MM/YYYY',
  isDate: true
});

ui.formInput('Time', 'DateTime', {

  css: 'time-item',
  mask: 'HH:mm',

  init: function(scope) {
    this._super(scope);

    scope.parse = function(value) {
      return value;
    };

    scope.format = function(value) {
      return value;
    };
  },

  link_editable: function(scope, element, attrs, model) {

    element.mask({
      mask: this.mask
    });

    element.change(function(e, ui) {
      updateModel();
    });

    element.on('keydown', function(e){
      if (e.isDefaultPrevented()) {
        return;
      }
      if (e.keyCode === $.ui.keyCode.ENTER || e.keyCode === $.ui.keyCode.TAB) {
        updateModel();
      }
    });

    element.on('grid:check', function () {
      updateModel();
    });

    scope.validate = function(value) {
      return !value || /^(\d+:\d+)$/.test(value);
    };

    function updateModel() {
      var masked = element.mask("value") || '',
        value = element.val() || '',
        oldValue = scope.getValue() || '';

      if (value && !element.mask("valid")) {
        return model.$setViewValue(value); // force validation
      }
      if (_.isEmpty(masked)) {
        value = null;
      }

      value = scope.parse(value);

      if (angular.equals(value, oldValue)) {
        return;
      }

      scope.setValue(value, true);
      scope.$applyAsync();
    }

    scope.$render_editable = function() {
      var value = scope.getText();
      if (value) {
        element.mask('value', value);
      } else {
        element.mask('value', '');
      }
    };
  },
  template_editable: '<input type="text">'
});

ui.formInput('RelativeTime', 'DateTime', {
  metaWidget: true,
  init: function(scope) {
    this._super(scope);

    scope.isReadonly = function() {
      return true;
    };

    scope.format = function(value) {
      if (value) {
        return moment(value).fromNow();
      }
      return "";
    };
  }
});

function formatDuration(field, value) {
  if (!value || !_.isNumber(value)) {
    return value;
  }

  var h = Math.floor(value / 3600);
  var m = Math.floor((value % 3600) / 60);
  var s = Math.floor((value % 3600) % 60);

  h = _.str.pad(h, field.big ? 3 : 2, '0');
  m = _.str.pad(m, 2, '0');
  s = _.str.pad(s, 2, '0');

  var text = h + ':' + m;

  if (field.seconds) {
    text = text + ':' + s;
  }

  return text;
}

ui.formatDuration = formatDuration;

ui.formInput('Duration', 'Time', {
  metaWidget: true,
  mask: '99:mm',

  init: function(scope) {
    this._super(scope);

    var field = scope.field;
    var pattern = /^\d+:\d+(:\d+)?$/;

    scope.format = function(value) {
      return formatDuration(field, value);
    };

    scope.parse = function(value) {
      if (!value || !_.isString(value)) {
        return value;
      }
      if (!pattern.test(value)) {
        return null;
      }

      var parts = value.split(':'),
        first = +(parts[0]),
        next = +(parts[1]),
        last = +(parts[2] || 0);

      return (first * 60 * 60) + (next * 60) + last;
    };
  },

  link_editable: function(scope, element, attrs, model) {
    var field = scope.field || {},
      mask = this.mask;

    if (field.big) {
      mask = "999:mm";
    }
    if (field.seconds) {
      mask = mask + ":mm";
    }

    this.mask = mask;
    this._super.apply(this, arguments);

    scope.validate = function(value) {
      return !value || _.isNumber(value);
    };
  }
});

})();
