###
# htdocs/coffee/bill/10_common.coffee
###
# shared with billing, tipping and options
ADDRMATCH = /(?:^|\s|bitcoin:|>|\/)([13][a-km-zA-HJ-NP-Z0-9]{26,33})(?:$|\"|\&|<|\s)/
# default values
DEFAULT_OPTS = {
  default: '1000', # 1000 bits ($.37 12/10/14)
  tipreply: "I am sending you {AMT} bits via https://www.syndicoin.co/",
  notipreply: 'I tried to send you {AMT} bits, but could not find your address.  Please see https://www.syndicoin.co/#!tipping'
}
LOADEDTYPE = 'DOMContentLoaded'
ADDEVENTLISTENER = 'addEventListener'
if not document[ADDEVENTLISTENER] # bail if IE<9
  return
# when document is ready
ready = (readycb) ->
  state = document.readyState
  if state == 'loading'
    document[ADDEVENTLISTENER] LOADEDTYPE, () ->
      document.removeEventListener LOADEDTYPE, self
      readycb()
  else
    readycb()
HTMLCollection.prototype.each = (cb) ->
  i = 0
  while node = @[i++]
    cb node
  return null
###
# htdocs/coffee/bill/12_constants.coffee
###
# to aid minification - things used in bill and badge
DBLUE = '#082F63'
LBLUE = '#116DDE'
GRAY = '#C8D6D4'
doc=document
win=window
NAMESPACE='syndicoin'
DOMAIN=NAMESPACE + '.co' # syndicoin.co
MSGID=NAMESPACE + '-message'
ROOT='https://www.' + DOMAIN
FONT = 'font-family:sans-serif;font-size:13px;font-weight:bold;text-decoration:none;line-height:1.2;'
BITCOIN='bitcoin:'
###
# htdocs/coffee/bill/20_bug.coffee
###
W3 = 'http://www.w3.org/'
svgImg = (size) ->
  scale = size/200
  ''
###
# htdocs/coffee/bill/30_fmt.coffee
###
formatsatoshi = (sat) ->
  rnd = Math.round
  if      sat > 100000000000
    return rnd(sat / 100000000000) + 'BTC'
  else if sat > 100000000
    return rnd(sat / 100000000) + 'M bits'
  else if sat > 100000
    return rnd(sat / 100000) + 'K bits'
  else
    return rnd(sat/100) + ' bits'
###
# htdocs/coffee/bill/40_parseqs.coffee
###
# parse query string
DECODE = decodeURIComponent
parseqs = (query) ->
  params = {}
  if query
    for val in query.split /[&;]/
      pair = val.split('=')
      params[DECODE(pair[0])] = DECODE(pair[1])
  return params
###
# htdocs/coffee/bill/5_ajax.coffee
###
ajax = (url, callback) ->
  xhr = new XMLHttpRequest()
  xhr.onreadystatechange = () ->
    if xhr.readyState == 4
      callback(if xhr.status == 200 then xhr.responseText else '')
  xhr.open "GET", url, true
  xhr.send()
###
# htdocs/coffee/bill/badge.coffee
###
ilblock = () ->
  span = doc.createElement('span')
  span.style.cssText = 'display:inline-block;vertical-align:top;'
  return span
# Detect visibility to suppress animation
# https://developer.mozilla.org/en-US/docs/Web/Guide/User_experience/Using_the_Page_Visibility_API
visibilityChange = hidden = null
visible = true
for prefix in ['h', 'mozH', 'msH', 'webkitH']
  hidden = prefix + 'idden'
  if doc[hidden]?
    visibilityChange = prefix.slice(0, -1) + 'visibilitychange'
    break
if visibilityChange
  doc[ADDEVENTLISTENER] visibilityChange, () ->
    visible = not doc[hidden]
getaddr = (badge) ->
  href = badge.href
  if not href or href[0..7] != BITCOIN
    return
  qs = parseqs(href.split('?')[1]) or {}
  addr = href.split(/[:?]/)[1]
  return addr
initval = {}
ws = null
addrs = []
afterInitVal = (elements) ->
  elements.each (badge) ->
    addr = getaddr badge
    if not addr
      console.error 'cannot find address', badge
      return
    degrees = 0
    badge.style.cssText = FONT + 'display:inline-block;height:18px;width:88px;border:1px solid ' + LBLUE + ';line-height: 18px;text-align:left;background-color:#fff;word-spacing:-2px;color:' + DBLUE + ';white-space:nowrap;overflow:hidden;border-radius:2px;box-shadow:' + GRAY + ' 1px 1px 1px;text-shadow: 1px 1px ' + GRAY + ';transition:background-color .5s;'
    badge.innerHTML = ''
    icon = ilblock()
    icon.style.cssText += 'padding:0 3px;transition:transform 1s;'
    badge.appendChild icon
    icon.innerHTML = svgImg(18)
    text = ilblock()
    badge.appendChild text
    badge.onmouseover = () ->
      @.style.color= LBLUE
      @.style.borderColor = DBLUE
    badge.onmouseout = () ->
      @.style.color = DBLUE
      @.style.borderColor = LBLUE
    badge.setAttribute 'title', 'SyndiCoin.co - contributions to date'
    subscribe = (satoshi) ->
      text.innerHTML = if satoshi then formatsatoshi(satoshi) else 'Firehose'
      if not WebSocket?
        return
      if not ws?
        ws = new WebSocket 'wss://socket.blockcypher.com/v1/btc/main?token=' + CHAINAPI
      timer = null
      ws[ADDEVENTLISTENER] 'message', (ev) ->
        msg = JSON.parse(ev.data)
        amt = 0
        for output in msg.outputs
          if addr == '*' or output.addresses[0] == addr
            amt += output.value
        satoshi += amt
        # animation only happens if page is visible
        if not visible or amt == 0
          return
        text.innerHTML = '+ ' + formatsatoshi(amt)
        degrees += 360
        badge.style.backgroundColor = GRAY
        icon.style.transform = "rotate(#{ degrees }deg)"
        if timer
          clearTimeout timer
        timer = setTimeout () ->
          badge.style.backgroundColor = 'transparent'
          text.innerHTML = formatsatoshi(satoshi)
        , 2000
      addrs.push addr
    if addr == '*' # test mode
      subscribe(0)
    else
      subscribe(initval[addr])
  if ws
    ws[ADDEVENTLISTENER] 'open', () ->
      req = {event: 'unconfirmed-tx'}
      if '*' in addrs
        ws.send JSON.stringify req
        return
      for addr in addrs
        req.address = addr
        ws.send JSON.stringify req
CHAINAPI='bc19915c188d1f28afd984919a388d58'
GEBCN = 'getElementsByClassName'
setupBadge = (root) ->
  cnt = 0
  elements = root[GEBCN]('syndicoin-badge')
  for badge in elements
    addr = getaddr badge
    if addr and addr != '*'
      cnt++
      ajax "https://api.blockcypher.com/v1/btc/main/addrs/" + addr + '/balance?token=' + CHAINAPI, (response) ->
        response = JSON.parse(response)
        initval[response.address] = response.total_received
        if --cnt == 0
          afterInitVal(elements)
ready () ->
  if syndicoin.badgeinit
    return
  syndicoin.badgeinit = true
  if not doc[GEBCN]?
    return
  setupBadge doc
###
# htdocs/coffee/bill/bill.coffee
###
# IE needs it in base64 format
MSGCSS = FONT + "display:inline-block;position:fixed;top:0;right:0;background:#C8D6D4 url('" + (if window.btoa then ('data:image/svg+xml;base64,' + window.btoa(svgImg(24))) else (ROOT + '/img/bug_sm.png')) + "') no-repeat 10px 3px;border-bottom-left-radius:1em;padding: .5em 1em .7em 40px;color: #116DDE;display:none;opacity:1;transition:opacity 1.5s;box-shadow:0 0 3px #353D3B inset;z-index:2000000000;max-height:3em;overflow:hidden;"
origin = doc.location.protocol + '//' + doc.location.host
if origin == 'http://test.' + DOMAIN or origin == 'https://testnet.' + DOMAIN
  ROOT = origin
INVOICE = ROOT + '/#!invoice'
opts = { scantext: true, default: DEFAULT_OPTS['default'] }
msg = null
# Walk the dom
# thanks http://www.javascriptcookbook.com/article/Traversing-DOM-subtrees-with-a-recursive-walk-the-DOM-function
walk = (node, cb) ->
  cb node
  node = node.firstChild
  while node
    walk(node, cb)
    node = node.nextSibling
# set up communication frame
frame = null
frameready = false
onready = []
win[ADDEVENTLISTENER] 'message', (ifrmsg) ->
  if ifrmsg.origin != ROOT
    return
  if ifrmsg.data != 'ready' and ifrmsg.data != 'first'
    return
  if ifrmsg.data == 'first'
    INVOICE = ROOT + '/#!'
    if msg
      msg.setAttribute 'href', INVOICE
  frameready = true
  while cb = onready.pop()
    cb()
# when frame is ready, call callback
whenready = (cb) ->
  if frameready
    cb()
    return
  onready.push cb
setupFrame = (callback) ->
  # no duplication - find frame inserted by my brother
  #  (document loaded vs. extension loaded)
  # message window
  if not msg
    msg = doc.getElementById(MSGID)
  if not msg
    msg=doc.createElement('a')
    msg.setAttribute 'id', MSGID
    msg.style.cssText = MSGCSS
    msg.setAttribute 'href', INVOICE
    msg.setAttribute 'target', '_blank'
    doc.body.appendChild msg
  # communication frame
  if not frame
    frame = doc.getElementById(NAMESPACE)
  if not frame
    frame=doc.createElement('iframe')
    frame.setAttribute 'id', NAMESPACE
    frame.setAttribute 'src', ROOT + '/endpoint.html'
    frame.style.cssText = "position:absolute;width:1px;height:1px;left:-9999px;"
    doc.body.appendChild frame
  whenready callback
fadeout = null
fadedone = null
setupBill = () ->
  pushData = () ->
    for d in arguments
      do (d) ->
        data = d
        if not data.amount
          data.amount = opts.default
          data.currency = 'bits'
        setupFrame () ->
          data.url = win.location.href
          data.title = doc.title
          # preview iframe does not save data:
          if not document.location.href.match(/^(about:|javascript:)/)
            frame.contentWindow.postMessage JSON.stringify(data), ROOT
          if data.hide == 'hide'
            return
          msg.innerHTML = "SyndiCoin #{ data.wallet[0..5] }... #{ data.amount } #{ data.currency }
" + msg.innerHTML
          msg.style.display = 'inline-block';
          msg.style.opacity='1';
          if fadeout
            clearTimeout fadeout
            clearTimeout fadedone
          fadeout = setTimeout () ->
            msg.style.opacity='0';
          , 3000
          fadedone = setTimeout () ->
            msg.style.display = 'none';
            msg.innerHTML = ''
          , 4500
  if win[NAMESPACE]
    pushData(win[NAMESPACE]...)
  win[NAMESPACE] = {
    push: pushData,
  }
  # on tipping site, no scan
  if doc.location.host.match /^([a-z]*\.reddit\.com|bitcointalk\.org|www\.facebook\.com|www\.tradingview\.com|twitter\.com)/
    return
  scan = () ->
    matches = []
    items = doc.getElementsByTagName('a')
    btclinks = []
    for link in items
      if link.href[0..7] == BITCOIN
        link.onclick = (ev) ->
          ev.preventDefault()
          win.open INVOICE
    for link in items
      href = link.href
      if href[0..7] == BITCOIN
        qs = parseqs(href.split('?')[1]) or {}
        data = {
          wallet: href.split(/[:?]/)[1],
          amount: link.getAttribute('data-amount') or qs.amount,
          currency: link.getAttribute('data-currency') or 'BTC',
          hide: link.getAttribute('data-hide') or null,
        }
        pushData data
        return
      # https://blockchain.info/address/1LEuvYqBwU9DW7gGkZtaPnVEJqd9jvNpYy
      bcmatch = href.match /https\:\/\/blockchain.info\/address\/([a-zA-Z0-9]+)/
      if bcmatch
        matches.push bcmatch[1]
    # https://github.com/priestc/Autotip
    if matches.length < 1
      for meta in doc.getElementsByTagName('meta')
        if meta.name == 'microtip'
          data = {
            wallet: meta.content,
            amount: meta.getAttribute('data-amount'),
            currency: (meta.getAttribute('data-currency') or 'btc').toLowerCase(),
            description: meta.getAttribute('data-recipient'),
          }
          ratio = parseFloat(meta.getAttribute('data-ratio'))
          if ratio
            data.amount = parseInt(opts.default * ratio)
            data.currency = 'bits'
          pushData data
    if matches.length < 1 and opts['scantext']
      walk document.body, (node) ->
        pname = node.parentNode.tagName.toLowerCase()
        if node.nodeType == 3 and pname != 'script' and pname != 'style'
          text = node.data
          match = text.match(ADDRMATCH)
          if match and matches[0] != match[1]
            matches.push match[1]
    if matches.length == 1
      pushData {
        wallet: matches[0],
      }
  if doc.readyState == 'loading'
    win[ADDEVENTLISTENER] 'load', scan, false
  else
    scan()
ready () ->
  # already loaded / on home site, bail out
  if doc.getElementById(NAMESPACE) or (origin == ROOT and doc.getElementById('intro'))
    return
  if chrome? and chrome.storage
    chrome.storage.sync.get null, (items) ->
      for k, v of items
        opts[k] = v
      setupBill()
  else
    setupBill()