/*
 * Copyright (c) 2010-2024 Belledonne Communications SARL.
 *
 * This file is part of linphone-desktop
 * (see https://www.linphone.org).
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
// =============================================================================
// Contains many common helpers.
// =============================================================================

.pragma library

.import QtQuick as QtQuick
.import Linphone as Linphone

// =============================================================================
// Constants.
// =============================================================================


var SCHEME_REGEX = new RegExp('^[^:]+:')

// =============================================================================
// QML helpers.
// =============================================================================

function buildCommonDialogUri (component) {
  return 'qrc:/ui/modules/Common/Dialog/' + component + '.qml'
}
function buildCommonUri (component, subComponent) {
  return 'qrc:/ui/modules/Common/'+subComponent+'/' + component + '.qml'
}

function buildLinphoneDialogUri (component) {
  return 'qrc:/ui/modules/Linphone/Dialog/' + component + '.qml'
}

function buildAppDialogUri (component) {
  return 'qrc:/ui/views/App/Dialog/' + component + '.qml'
}

// -----------------------------------------------------------------------------

// Destroy timeout.
function clearTimeout (timer) {
  timer.stop() // NECESSARY.
  timer.destroy()
}

// -----------------------------------------------------------------------------

function createObject (source, parent, options) {
  if (options && options.isString) {
    var object = Qt.createQmlObject(source, parent)

    var properties = options && options.properties
    if (properties) {
      for (var key in properties) {
        object[key] = properties[key]
      }
    }

    return object
  }

  var component = Qt.createComponent(source)
  if (component.status !== QtQuick.Component.Ready) {
    console.debug('Component not ready.')
    if (component.status === QtQuick.Component.Error) {
      console.debug('Error: ' + component.errorString())
    }
    return // Error.
  }

  var object = component.createObject(parent, (options && options.properties) || {})
  if (!object) {
    console.debug('Error: unable to create dynamic object.')
  }

  return object
}

// -----------------------------------------------------------------------------

function getSystemPathFromUri (uri) {
  var str = uri.toString()
  if (startsWith(str, 'file://')) {
    str = str.substring(7)

    // Absolute path.
    if (str.charAt(0) === '/') {
      return runOnWindows() ? str.substring(1) : str
    }
  }

  return str
}

function getUriFromSystemPath (path) {
  if (path.startsWith('file://')) {
    return path
  }

  if (runOnWindows()) {
    return 'file://' + (/^\w:/.exec(path) ? '/' : '') + path
  }

  return 'file://' + path
}

// -----------------------------------------------------------------------------

// Returns the top (root) parent of one object.
function getTopParent (object, useFakeParent) {
  function _getTopParent (object, useFakeParent) {
    return (useFakeParent && object.$parent) || object.parent
  }

  var parent = _getTopParent(object, useFakeParent)
  var p
  while ((p = _getTopParent(parent, useFakeParent)) != null) {
    parent = p
  }

  return parent
}

// -----------------------------------------------------------------------------

// Load by default a window in the ui/views folder.
// If options.isString is equals to true, a marshalling component can
// be used.
//
// Supported options: isString, exitHandler, properties.
//
// If exitHandler is used, window must implement exitStatus signal.
function openWindow (window, parent, options, fullscreen) {
  var object = createObject(window, parent, options)

  object.closing.connect(object.destroy.bind(object))

  if (options && options.exitHandler) {
    object.exitStatus.connect(
      // Bind to access parent properties.
      options.exitHandler.bind(parent)
    )
  }
  if( runOnWindows()){
    object.show() // Needed for Windows : Show the window in all case. Allow to graphically locate the window before going to fullscreen.
    if(fullscreen)
      object.showFullScreen()// Should be equivalent to changing visibility
  }else if(fullscreen)
      object.showFullScreen()// Should be equivalent to changing visibility
  else
    object.show()
  return object
}

// -----------------------------------------------------------------------------

function resolveImageUri (name) {
  return name
    //? 'image://internal/' + removeScheme(Qt.resolvedUrl('/assets/images/' + name + '.svg'))
    ? 'image://internal/' + name
    : ''
}

// -----------------------------------------------------------------------------


function runOnWindows () {
  var os = Qt.platform.os
  return os === 'windows' || os === 'winrt'
}

// -----------------------------------------------------------------------------

// Test if a point is in a item.
//
// `source` is the item that generated the point.
// `target` is the item to test.
// `point` is the point to test.
function pointIsInItem (source, target, point) {
  point = source.mapToItem(target.parent, point.x, point.y)

  return (
    point.x >= target.x &&
    point.y >= target.y &&
    point.x < target.x + target.width &&
    point.y < target.y + target.height
  )
}

// -----------------------------------------------------------------------------

// Test the type of a qml object.
// Warning: this function is probably not portable
// on new versions of Qt.
//
// So, if you want to use it on a specific `className`, please to add
// a test in `test_qmlTypeof_data` of `utils.spec.qml`.
function qmlTypeof (object, className) {
  var str = object.toString()

  return (
    str.indexOf(className + '(') == 0 ||
    str.indexOf(className + '_QML') == 0
  )
}

// -----------------------------------------------------------------------------

function removeScheme (url) {
  return url.toString().replace(SCHEME_REGEX, '')
}

// -----------------------------------------------------------------------------

// A copy of `Window.setTimeout` from js.
// delay is in milliseconds.
function setTimeout (parent, delay, cb) {
  var timer = new (function (parent) {
    return Qt.createQmlObject('import QtQml 2.2; Timer { }', parent)
  })(parent)

  timer.interval = delay
  timer.repeat = false
  timer.triggered.connect(cb)
  timer.start()

  return timer
}

// =============================================================================
// GENERIC.
// =============================================================================

function _computeOptimizedCb (func, context) {
  return context
    ? (function () {
      return func.apply(context, arguments)
    }) : func
}

function _indexFinder (array, cb, context) {
  var length = array.length

  for (var i = 0; i < length; i++) {
    if (cb( (array.getAt ? array.getAt(i) : array[i]), i, array)) {
      return i
    }
  }

  return -1
}

function _keyFinder (obj, cb, context) {
  var keys = Object.keys(obj)
  var length = keys.length

  for (var i = 0; i < length; i++) {
    var key = keys[i]
    if (cb(obj[key], key, obj)) {
      return key
    }
  }
}

// -----------------------------------------------------------------------------

// Basic assert function.
function assert (condition, message) {
  if (!condition) {
    throw new Error('Assert: ' + message)
  }
}

// -----------------------------------------------------------------------------

function basename (str) {
  if (!str) {
    return ''
  }

  if (runOnWindows()) {
    str = str.replace(/\\/g, '/')
  }

  var str2 = str
  var length = str2.length - 1

  if (str2[length] === '/') {
    str2 = str2.substring(0, length)
  }

  return str2.slice(str2.lastIndexOf('/') + 1)
}

// -----------------------------------------------------------------------------

function capitalizeFirstLetter (str) {
  return str.charAt(0).toUpperCase() + str.slice(1)
}

// -----------------------------------------------------------------------------

function dirname (str) {
  if (!str) {
    return ''
  }

  if (runOnWindows()) {
    str = str.replace(/\\/g, '/')
  }

  var str2 = str
  var length = str2.length - 1

  if (str2[length] === '/') {
    str2 = str2.substring(0, length)
  }

  return str2.slice(0, str2.lastIndexOf('/') + 1)
}

// -----------------------------------------------------------------------------

function extractProperties (obj, pattern) {
  if (!pattern) {
    return {}
  }

  var obj2 = {}
  pattern.forEach(function (property) {
    obj2[property] = obj[property]
  })

  return obj2
}

// -----------------------------------------------------------------------------

// Returns an array from an `object` or `array` argument.
function ensureArray (obj) {
  if (isArray(obj)) {
    return obj
  }

  var keys = Object.keys(obj)
  var length = keys.length
  var values = Array(length)

  for (var i = 0; i < length; i++) {
    values[i] = obj[keys[i]]
  }

  return values
}

// -----------------------------------------------------------------------------

function escapeQuotes (str) {
  return str != null
    ? str.replace(/([^'\\]*(?:\\.[^'\\]*)*)'/g, '$1\\\'')
    : ''
}

function decode(str){
  return decodeURIComponent(escape(str.replace(/%([0-9A-Fa-f]{2})/g, function() {
    return String.fromCharCode(parseInt(arguments[1], 16));
  })));
}
// -----------------------------------------------------------------------------

// Get the first matching value in a array or object.
// The matching value is obtained if `cb` returns true.
function find (obj, cb, context) {
  cb = _computeOptimizedCb(cb, context)

  var finder = isArray(obj) ? _indexFinder : _keyFinder
  var key = finder(obj, cb, context)

  return key != null && key !== -1 ? obj[key] : null
}

// -----------------------------------------------------------------------------

function findIndex (array, cb, context) {
  cb = _computeOptimizedCb(cb, context)

  var key = _indexFinder(array, cb, context)
  return key != null ? key : -1
}

// -----------------------------------------------------------------------------
function formatElapsedTime (seconds) {
  seconds = parseInt(seconds, 10)
//s,	m,	h,		d,		W,		M,			Y
//1,	60,	3600,	86400,	604800,	2592000,	31104000
	var y = Math.floor(seconds / 31104000)
	if(y > 0)
		return y+ ' years'
	var M = Math.floor(seconds / 2592000)
	if(M > 0)
		return M+' months'
	var w = Math.floor(seconds / 604800)
	if(w>0)
		return w+' week';
	var d = Math.floor(seconds / 86400)
	if(d>0)
		return d+' days'
	var h = Math.floor(seconds / 3600)
	var m = Math.floor((seconds - h * 3600) / 60)
	var s = seconds - h * 3600 - m * 60
	
	if (h < 10 && h > 0) {
		h = '0' + h
	}
	
	if (m < 10) {
		m = '0' + m
	}
	
	if (s < 10) {
		s = '0' + s
	}
	
	return (h === 0 ? '' : h + ':') + m + ':' + s
}

function formatDuration (seconds) {
  seconds = parseInt(seconds, 10)
//s,	m,	h,		d,		W,		M,			Y
//1,	60,	3600,	86400,	604800,	2592000,	31104000
	var y = Math.floor(seconds / 31104000)
	if(y > 0)
	//: '%1 year'
		return qsTr('formatYears', '', y).arg(y)
	var M = Math.floor(seconds / 2592000)
	if(M > 0)
		//: '%1 month'
		return qsTr('formatMonths', '', M).arg(M)
	var w = Math.floor(seconds / 604800)
	if(w>0)
		//: '%1 week'
		return qsTr('formatWeeks', '', w).arg(w)
	var d = Math.floor(seconds / 86400)
	if(d>0)
		//: '%1 day'
		return qsTr('formatDays', '', d).arg(d)
	var h = Math.floor(seconds / 3600)
	var m = Math.floor((seconds - h * 3600) / 60)
	var s = seconds - h * 3600 - m * 60
	
	//: '%1 hour'
	return  (h > 0 ? qsTr('formatHours', '', h).arg(h): '')
	//: '%1 minute'
			+ (m > 0 ? (h > 0 ? ', ' : '') +qsTr('formatMinutes', '', m).arg(m): '')
	//: '%1 second'
			+ (s > 0 ? (h> 0 || m > 0 ? ', ' : '') +qsTr('formatSeconds', '', s).arg(s): '')
}

function buildDate(date, time){
	var dateTime = new Date()
	dateTime.setFullYear(date.getFullYear(), date.getMonth(), date.getDate())
	dateTime.setHours(time.getHours())
	dateTime.setMinutes(time.getMinutes())
	dateTime.setSeconds(time.getSeconds())
	return dateTime
}

function equalDate(date1, date2){
    return date1.getFullYear() == date2.getFullYear() && date1.getMonth() == date2.getMonth() && date1.getDate() == date2.getDate()
}

function fromUTC(date){
	return new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(),
                date.getUTCDate(), date.getUTCHours(),
                date.getUTCMinutes(), date.getUTCSeconds()));
}
// -----------------------------------------------------------------------------

function formatSize (size) {
  var units = ['KB', 'MB', 'GB', 'TB']
  var unit = 'B'

  if (size == null) {
    size = 0
  }

  var length = units.length
  for (var i = 0; size >= 1024 && i < length; i++) {
    unit = units[i]
    size /= 1024
  }

  return parseFloat(size.toFixed(2)) + unit
}

// -----------------------------------------------------------------------------

// Generate a random number in the [min, max[ interval.
// Uniform distrib.
function genRandomNumber (min, max) {
  return Math.random() * (max - min) + min
}

function genRandomColor(){
	return '#'+ Math.floor(Math.random()*255).toString(16)
									  +Math.floor(Math.random()*255).toString(16)
									  +Math.floor(Math.random()*255).toString(16)
}

// -----------------------------------------------------------------------------

// Generate a random number between a set of intervals.
// The `intervals` param must be orderer like this:
// `[ [ 1, 4 ], [ 8, 16 ], [ 22, 25 ] ]`
function genRandomNumberBetweenIntervals (intervals) {
  if (intervals.length === 1) {
    return genRandomNumber(intervals[0][0], intervals[0][1])
  }

  // Compute the intervals size.
  var size = 0
  intervals.forEach(function (interval) {
    size += interval[1] - interval[0]
  })

  // Generate a value in the interval: `[0, size[`
  var n = genRandomNumber(0, size)

  // Map the value in the right interval.
  n += intervals[0][0]
  for (var i = 0; i < intervals.length - 1; i++) {
    if (n < intervals[i][1]) {
      break
    }

    n += intervals[i + 1][0] - intervals[i][1]
  }

  return n
}

// -----------------------------------------------------------------------------

// Returns the extension of a filename.
function getExtension (str) {
  var index = str.lastIndexOf('.')

  if (index === -1) {
    return ''
  }

  return str.slice(index + 1)
}

// -----------------------------------------------------------------------------

// Test if a value is included in an array or object.
function includes (obj, value, startIndex) {
  obj = ensureArray(obj)
  if (startIndex == null) {
    startIndex = 0
  }
  var length = obj.length

  for (var i = startIndex; i < length; i++) {
    if (
      value == obj[i] ||
      // Check `NaN`.
      (value !== value && obj[i] !== obj[i])
    ) {
      return true
    }
  }
  return false
}

// -----------------------------------------------------------------------------

function isArray (array) {
  return (array instanceof Array)
}

// -----------------------------------------------------------------------------

function isFunction (func) {
  return typeof func === 'function'
}

// -----------------------------------------------------------------------------

function isInteger (integer) {
  return integer === parseInt(integer, 10)
}

// -----------------------------------------------------------------------------

function isObject (object) {
  return object !== null && typeof object === 'object'
}

// -----------------------------------------------------------------------------

function isString (string) {
  return typeof string === 'string' || string instanceof String
}

// -----------------------------------------------------------------------------

// Convert a snake_case string to a lowerCamelCase string.
function snakeToCamel (s) {
  return s.replace(/(\_\w)/g, function (matches) {
    return matches[1].toUpperCase()
  })
}

// -----------------------------------------------------------------------------

// Test if a string starts by a given string.
function startsWith (str, searchStr) {
  if (searchStr == null) {
    searchStr = ''
  }

  return str.slice(0, searchStr.length) === searchStr
}

// -----------------------------------------------------------------------------

// Invoke a `cb` function with each value of the interval: `[0, n[`.
// Return a mapped array created with the returned values of `cb`.
function times (n, cb, context) {
  var arr = Array(Math.max(0, n))
  cb = _computeOptimizedCb(cb, context, 1)

  for (var i = 0; i < n; i++) {
    arr[i] = cb(i)
  }

  return arr
}

// -----------------------------------------------------------------------------

function unscapeHtml (str) {
  return str.replace(/&/g, '&amp;')
    .replace(/</g, '\u2063&lt;')
    .replace(/>/g, '\u2063&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#039;')
}

// -----------------------------------------------------------------------------

function write (fileName, text) {
  // TODO: Deal with async.
  var request = new XMLHttpRequest()
  request.open('PUT', getUriFromSystemPath(fileName), false)
  request.send(text)
}

function computeAvatarSize (container, maxSize, ratio) {
  var height = container.height * ratio
  var width = container.width * ratio
  var size = height < maxSize && height > 0 ? height : maxSize
  return size < width ? size : width
}

// -----------------------------------------------------------------------------

function openCodecOnlineInstallerDialog (window, codecInfo, cb) {
  var VideoCodecsModel = Linphone.VideoCodecsModel
  window.attachVirtualWindow(buildCommonDialogUri('ConfirmDialog'), {
    descriptionText: qsTr('downloadCodecDescription')
      .replace('%1', codecInfo.mime)
      .replace('%2', codecInfo.encoderDescription)
  }, function (status) {
    if (status) {
      window.attachVirtualWindow(buildLinphoneDialogUri('OnlineInstallerDialog'), {
        downloadUrl: codecInfo.downloadUrl,
        extract: true,
        installFolder: VideoCodecsModel.codecsFolder,
        installName: codecInfo.installName,
        mime: codecInfo.mime,
        checksum: codecInfo.checksum
      }, function (status) {
        if (status) {
          VideoCodecsModel.reload()
        }
        if (cb) {
          cb(window)
        }
      })
    }
    else if (cb) {
      cb(window)
    }
  })
}

function printObject(o) {
  var out = '';
  for (var p in o) {
    out += p + ': ' + o[p] + '\n';
  }
  if(!o)
    return 'Empty'
  else
    return out;
}

function infoDialog(window, message) {
	window.attachVirtualWindow(buildCommonDialogUri('ConfirmDialog'), {
		buttonTexts : ['',qsTr('okButton')],
		descriptionText: message,
		showButtonOnly: 1
	}, function (status) {})
}
