React = require 'react'
createClass = require 'create-react-class'
moment = require 'moment'
_ = require 'lodash'

{div, a} = React.DOM

module.exports =
  
  ###
    * Return Age from datestring
    * @param {String} datestring
    * @param {Boolean} return the month default false.
  ###
  formatAge: (datestring, includeMonth) ->
    now  = moment().utc()
    birthDate = moment(datestring).utc()
    
    age = ''
    months = now.diff(birthDate, 'months')

    if months < 1
      days = now.diff(birthDate, 'days')
      age = "#{days} DO"
    else if months < 48
      age = "#{months} MO"
    else
      years = now.diff(birthDate, 'years')
      age = "#{years} YO"

      # Include month in age
      if includeMonth
        months = months-(years*12)
        # don't include if 0 months
        if months then age = "#{years} yrs #{months} mnth"

    return age

  formatNHS: (nhs = '') ->
    # Strip non digit characters out of the nhs
    nhs = nhs.replace(/[^0-9]/g, "")
    
    nhs1 = nhs.substr(0,3)
    nhs2 = nhs.substr(3,3)
    nhs3 = nhs.substr(6,4)

    rv = "#{nhs1}"
    rv += " #{nhs2}" if nhs2 
    rv += " #{nhs3}" if nhs3
    rv
    

  # Returns 10 digit number in (xxx) xxx-xxxx format
  # or 7 digit number in xxx-xxxx format
  formatPhoneNumber: (phone) ->
    if phone? 
      # Make sure the number is a String
      phone = "#{phone}"
      phoneClone = phone
      phoneClone = phoneClone.replace(/[^0-9]/g, '')
      if phoneClone.length == 10
        phoneClone = phoneClone.replace(/(\d{3})(\d{3})(\d{4})/, "($1) $2-$3")
        return phoneClone
      if phoneClone.length == 7
        phoneClone = phoneClone.replace(/(\d{3})(\d{4})/, "$1-$2")
        return phoneClone
      else
        return phone
    else
      return ""

  formatSource: (options) ->
    {orgName, facilityName, systemName} = options

    sourceName = ''

    # Org
    if orgName?
      sourceName += orgName   
    # Facility
    if facilityName? and facilityName not in [orgName, '']
      sourceName += ' - ' if sourceName isnt ''
      sourceName += facilityName
    
    # System
    if (facilityName? or orgName?) and systemName? and systemName isnt ''
      sourceName += ' - ' if sourceName isnt ''
      sourceName += systemName

    sourceName

  formatTimezoneForAPI: (relevantDate) ->
    minutes = moment(relevantDate).utcOffset()
    
    if minutes < 0 then negative = "-"
    else negative = "+"
    
    minutes = Math.abs(minutes)
    
    hours = Math.floor(minutes / 60)
    if hours < 10 then hours = "0" + hours
    
    minutestring = minutes % 60
    if minutestring < 10  then minutestring = "0" + minutestring
    
    "#{negative}#{hours}:#{minutestring}"

  escapeRegExp: (string) ->
    string.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1")

  isValidEmail : (addr) ->
    # modified RFC 2822 - http://www.regular-expressions.info/email.html
    return /[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/.test addr

  # Adding comment to jenkins/github integration.
  # Converts raw bytes to KB, MB, etc
  bytesToSize: (bytes, precision) ->
    unless bytes? then return ''
    kilobyte = 1024
    megabyte = kilobyte * 1024
    gigabyte = megabyte * 1024
    terabyte = gigabyte * 1024
    
    if bytes >= 0 and bytes < kilobyte then return bytes + ' B'
    else if bytes >= kilobyte and bytes < megabyte then return (bytes / kilobyte).toFixed(precision) + ' KB'
    else if bytes >= megabyte and bytes < gigabyte then return (bytes / megabyte).toFixed(precision) + ' MB'
    else if bytes >= gigabyte and bytes < terabyte then return (bytes / gigabyte).toFixed(precision) + ' GB'
    else if bytes >= terabyte then return (bytes / terabyte).toFixed(precision) + ' TB'
    else return bytes + ' B'

  measureScrollBarWidth: ->
    $tester = $("<div id='outer' style='overflow: scroll; height: 500px; width: 500px; position: absolute; top: 100px; left: 100px;'><div id='inner' style='position: absolute; height: 100%; width: 100%;'></div><div style='height: 600px; width: 600px;'></div></div>")
    $('body').append $tester
    scrollBarWidth = $tester.height() - $tester.find('#inner').height()
    $tester.remove()
    return scrollBarWidth

  # Reduces name to targetLength by replacing a middle portion of the name with an ellipses
  formatFileName: (name, targetLength) ->
    
    # Get the starting file name length
    fileLength = name.length
    # Do nothing to files with less than targetLength characters
    if fileLength <= targetLength then return name 
    # Calculate the number of characters that need to be removed to get the filename down to targetLength
    # Add 3 to file length as the file length will technically get larger with the ellipses thus we need to increase the file name length
    # NOTE : it was adding 3 to the targetLength but doing that caused for overlapping characters in the file name    
    removeCount = (fileLength + 3) - targetLength
    # Figure out how far from the end we should keeps characters
    # In most cases this will be 6 characters unless the diff between the target length and the removeCount is 6 or else
    # This will insure the beginning of the file has more than 1 or 2 characters before the ellipses (unless the target length is extremely small like 3)
    if (targetLength - removeCount) <= 6 then charactersFromEnd = 4 else charactersFromEnd = 6
    # Calculate the place the remove should end
    endRemove = fileLength - charactersFromEnd
    # Calculate the postion to start the remove
    startRemove = endRemove - removeCount
    # NOTE: Negative start values cannot be used in substr becuase IE8 does not support them!
    # Get the start of the file
    fileStart = name.substr(0, startRemove)
    # Get the end of the file
    fileEnd = name.substr(endRemove)
    
    return fileStart + "…" + fileEnd

  # Gets file extensions from a file name, returns empty string when there is no extensions
  parseFileExtension: (filename) ->
    filenameSplit = filename.split('.')
    return name = if filenameSplit.length > 1 then filenameSplit[filenameSplit.length - 1] else ''

  # Makes a temparay (somewhat unique) id for use in the GUI before saving a new object to the server
  makeGuid: ->
    'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace /[xy]/g, (c) ->
      r = Math.random()*16|0
      v = if c is 'x' then r else (r&0x3|0x8)
      v.toString(16)

  # Checks if two object have the same id or guid
  idsMatch: (itemA, itemB) -> 
    unless itemA? and itemB? then return no
    
    if itemA.id? and itemB.id?
      return itemA.id is itemB.id
    else if itemA.guid? and itemB.guid?
      return itemA.guid is itemB.guid
    else
      return no

  # Create a fake mouse event
  synthesizeMouseEvent: (target, type, options = {}) ->
    event = target.ownerDocument.createEvent('MouseEvents')
    opts =
      type: type
      canBubble: false # Defaults to false: may need to set to true if we run into a situation where React has issues with events not bubbling to the top
      cancelable: true
      view: target.ownerDocument.defaultView
      detail: 1
      screenX: 0 # The coordinates within the entire page
      screenY: 0
      clientX: 0 # The coordinates within the viewport
      clientY: 0
      ctrlKey: false
      altKey: false
      shiftKey: false
      metaKey: false 
      button: 0 # 0 = left, 1 = middle, 2 = right
      relatedTarget: null

    # Merge the options with the defaults
    _.assign(opts, options)

    # Pass in the options
    event.initMouseEvent(
      opts.type
      opts.canBubble
      opts.cancelable
      opts.view
      opts.detail
      opts.screenX
      opts.screenY
      opts.clientX
      opts.clientY
      opts.ctrlKey
      opts.altKey
      opts.shiftKey
      opts.metaKey
      opts.button
      opts.relatedTarget
    )

    # Fire the event
    target.dispatchEvent(event)

  # Checks to see if input is a number
  isNumber: (input) ->
    return !isNaN(parseFloat(input)) and isFinite(input)

  # Limits a value to only two decimal places
  toFixed: (value) ->
    return Math.round(value * 100) / 100

  # Can measure widths and heights of DOM elements
  measureDOMProp: (el, DOMProp) ->
    measurer = document.getElementById 'measurer'
    measurer.appendChild el
    prop = el[DOMProp]
    measurer.removeChild el
    prop

  # Make sure URL begins with 'https'
  checkHTTPS: (url) ->
    checkString = url.slice 0, 5
    return checkString is "https"

  buildFormData: (context = @, newData = {}) ->
    refs = context['refs']
    unless refs? then throw Error("Form Data Builder: No 'refs' object found on context #{context}")

    ####################
    # Define the iterate function that will run for each key part
    ####################
    iterate = (data, keyParts, getValue) =>
      # Nested properties
      cur = keyParts[0]
      next = data
      matchArray = cur.match(/\[(\d+)\]/)
      matchCollection = cur.match(/\[\{(.+)\}\]/)
      nextIsArray = keyParts[1].match(/\[(.+)\]/) if keyParts[1]?
      arrayKey = matchArray[1] if matchArray?
      collectionKey = matchCollection[1].split(':') if matchCollection?

      # Check the object to establish the default type
      # Arrays
      if arrayKey
        unless data? then data = []
        next = data[arrayKey]
        cur = arrayKey
      
      # Collections
      else if collectionKey
        unless data? then data = []
        collectionRecord = null
        k = collectionKey[0]
        v = collectionKey[1]

        # Check if the value contains a state variable
        valRef = v.match(/\$(\w+)/)
        if valRef?
          val = context['state']

          # Make sure state is defined
          unless val? then throw Error("Form Data Builder: unable to pull state properties when local state is undefined")

          # Double-nested refs
          subKeys = v.replace(/^\$/, '').split('.')
          while subKeys.length
            val = val[subKeys.shift()]
          v = val

        for obj, i in data when obj?
          # Cast the prop value to a string, in case of a number/boolean type
          if obj[k]? and String(obj[k]) is String(v)
            collectionRecord = next = data[i]
            break

        unless collectionRecord?
          # If no record is found, push a new record in with the matching property
          d = {}
          d[k] = v
          data.push(d)
          next = data[data.length - 1]

      # Object
      else if not data[cur]?
        next = data[cur] = if nextIsArray then [] else {}

      # If it already exists
      else
        next = data[cur]

      # Base case. Return the value of the field once the destination prop has been reached
      unless keyParts.length > 1
        data[cur] = getValue()
        return data

      # Remove the first item to continue recursion
      keyParts.shift()

      return iterate(next, keyParts, getValue)
    
    ####################
    # Loop through the refs, running iterate for each key part
    # Refs can be skipped by prepending with '!'
    ####################
    for key, ref of refs when key[0] isnt '!' and ref.getValue?
      # Check for nested properties
      if key.match(/\.(?![^[\]]*])/)
        keyParts = key.split(/\.(?![^[\]]*])/g)    
        getValue = ref.getValue

        # Don't move on unless getValue exists
        unless getValue? then throw Error("Ref #{key} does not have a getValue method defined")

        # Iterate over each key
        iterate(newData, keyParts, getValue)
      
      # Set properties directly when not using nesting
      else
        newData[key] = ref.getValue()
    
    return newData





