var CHAR_TILDE = 126
var CODE_FNC1 = 102
 
var SET_STARTA = 103
var SET_STARTB = 104
var SET_STARTC = 105
var SET_SHIFT = 98
var SET_CODEA = 101
var SET_CODEB = 100
var SET_STOP = 106
 
var REPLACE_CODES = {
  CHAR_TILDE: CODE_FNC1 // ~ corresponds to FNC1 in GS1-128 standard
}
 
var CODESET = {
  ANY: 1,
  AB: 2,
  A: 3,
  B: 4,
  C: 5
}
 
function getBytes (str) {
  var bytes = []
  for (var i = 0; i < str.length; i++) {
    bytes.push(str.charCodeAt(i))
  }
  return bytes
}
 
var code128 = function (ctx, text, width, height) {
  width = parseInt(width)
 
  height = parseInt(height)
 
  var codes = stringToCode128(text)
 
  var g = new Graphics(ctx, width, height)
 
  var barWeight = g.area.width / ((codes.length - 3) * 11 + 35)
 
  var x = g.area.left
  var y = g.area.top
  for (var i = 0; i < codes.length; i++) {
    var c = codes[i]
    // two bars at a time: 1 black and 1 white
    for (var bar = 0; bar < 8; bar += 2) {
      var barW = PATTERNS[c][bar] * barWeight
      // var barH = height - y - this.border;
      var barH = height - y
      var spcW = PATTERNS[c][bar + 1] * barWeight
 
      // no need to draw if 0 width
      if (barW > 0) {
        g.fillFgRect(x, y, barW, barH)
      }
 
      x += barW + spcW
    }
  }
 
  ctx.draw()
}
 
function stringToCode128 (text) {
  var barc = {
    currcs: CODESET.C
  }
 
  var bytes = getBytes(text)
  // decide starting codeset
  var index = bytes[0] === CHAR_TILDE ? 1 : 0
 
  var csa1 = bytes.length > 0 ? codeSetAllowedFor(bytes[index++]) : CODESET.AB
  var csa2 = bytes.length > 0 ? codeSetAllowedFor(bytes[index++]) : CODESET.AB
  barc.currcs = getBestStartSet(csa1, csa2)
  barc.currcs = perhapsCodeC(bytes, barc.currcs)
 
  // if no codeset changes this will end up with bytes.length+3
  // start, checksum and stop
  var codes = []
 
  switch (barc.currcs) {
    case CODESET.A:
      codes.push(SET_STARTA)
      break
    case CODESET.B:
      codes.push(SET_STARTB)
      break
    default:
      codes.push(SET_STARTC)
      break
  }
 
  for (var i = 0; i < bytes.length; i++) {
    var b1 = bytes[i] // get the first of a pair
    // should we translate/replace
    if (b1 in REPLACE_CODES) {
      codes.push(REPLACE_CODES[b1])
      i++ // jump to next
      b1 = bytes[i]
    }
 
    // get the next in the pair if possible
    var b2 = bytes.length > (i + 1) ? bytes[i + 1] : -1
 
    codes = codes.concat(codesForChar(b1, b2, barc.currcs))
    // code C takes 2 chars each time
    if (barc.currcs === CODESET.C) i++
  }
 
  // calculate checksum according to Code 128 standards
  var checksum = codes[0]
  for (var weight = 1; weight < codes.length; weight++) {
    checksum += (weight * codes[weight])
  }
  codes.push(checksum % 103)
 
  codes.push(SET_STOP)
 
  // encoding should now be complete
  return codes
 
  function getBestStartSet (csa1, csa2) {
    // tries to figure out the best codeset
    // to start with to get the most compact code
    var vote = 0
    vote += csa1 === CODESET.A ? 1 : 0
    vote += csa1 === CODESET.B ? -1 : 0
    vote += csa2 === CODESET.A ? 1 : 0
    vote += csa2 === CODESET.B ? -1 : 0
    // tie goes to B due to my own predudices
    return vote > 0 ? CODESET.A : CODESET.B
  }
 
  function perhapsCodeC (bytes, codeset) {
    for (var i = 0; i < bytes.length; i++) {
      var b = bytes[i]
      if ((b < 48 || b > 57) && b !== CHAR_TILDE) { return codeset }
    }
    return CODESET.C
  }
 
  // chr1 is current byte
  // chr2 is the next byte to process. looks ahead.
  function codesForChar (chr1, chr2, currcs) {
    var result = []
    var shifter = -1
 
    if (charCompatible(chr1, currcs)) {
      if (currcs === CODESET.C) {
        if (chr2 === -1) {
          shifter = SET_CODEB
          currcs = CODESET.B
        } else if ((chr2 !== -1) && !charCompatible(chr2, currcs)) {
          // need to check ahead as well
          if (charCompatible(chr2, CODESET.A)) {
            shifter = SET_CODEA
            currcs = CODESET.A
          } else {
            shifter = SET_CODEB
            currcs = CODESET.B
          }
        }
      }
    } else {
      // if there is a next char AND that next char is also not compatible
      if ((chr2 !== -1) && !charCompatible(chr2, currcs)) {
        // need to switch code sets
        switch (currcs) {
          case CODESET.A:
            shifter = SET_CODEB
            currcs = CODESET.B
            break
          case CODESET.B:
            shifter = SET_CODEA
            currcs = CODESET.A
            break
        }
      } else {
        // no need to shift code sets, a temporary SHIFT will suffice
        shifter = SET_SHIFT
      }
    }
 
    // ok some type of shift is nessecary
    if (shifter !== -1) {
      result.push(shifter)
      result.push(codeValue(chr1))
    } else {
      if (currcs === CODESET.C) {
        // include next as well
        result.push(codeValue(chr1, chr2))
      } else {
        result.push(codeValue(chr1))
      }
    }
    barc.currcs = currcs
 
    return result
  }
}
 
// reduce the ascii code to fit into the Code128 char table
function codeValue (chr1, chr2) {
  if (typeof chr2 === 'undefined') {
    return chr1 >= 32 ? chr1 - 32 : chr1 + 64
  } else {
    return parseInt(String.fromCharCode(chr1) + String.fromCharCode(chr2))
  }
}
 
function charCompatible (chr, codeset) {
  var csa = codeSetAllowedFor(chr)
  if (csa === CODESET.ANY) return true
  // if we need to change from current
  if (csa === CODESET.AB) return true
  if (csa === CODESET.A && codeset === CODESET.A) return true
  if (csa === CODESET.B && codeset === CODESET.B) return true
  return false
}
 
function codeSetAllowedFor (chr) {
  if (chr >= 48 && chr <= 57) {
    // 0-9
    return CODESET.ANY
  } else if (chr >= 32 && chr <= 95) {
    // 0-9 A-Z
    return CODESET.AB
  } else {
    // if non printable
    return chr < 32 ? CODESET.A : CODESET.B
  }
}
 
var Graphics = function (ctx, width, height) {
  this.width = width
  this.height = height
  this.quiet = Math.round(this.width / 40)
 
  this.border_size = 0
  this.padding_width = 0
 
  this.area = {
    width: width - this.padding_width * 2 - this.quiet * 2,
    height: height - this.border_size * 2,
    top: this.border_size - 4,
    left: this.padding_width + this.quiet
  }
 
  this.ctx = ctx
  this.fg = '#000000'
  this.bg = '#ffffff'
 
  // fill background
  this.fillBgRect(0, 0, width, height)
 
  // fill center to create border
  this.fillBgRect(0, this.border_size, width, height - this.border_size * 2)
}
 
// use native color
Graphics.prototype._fillRect = function (x, y, width, height, color) {
  this.ctx.setFillStyle(color)
  this.ctx.fillRect(x, y, width, height)
}
 
Graphics.prototype.fillFgRect = function (x, y, width, height) {
  this._fillRect(x, y, width, height, this.fg)
}
 
Graphics.prototype.fillBgRect = function (x, y, width, height) {
  this._fillRect(x, y, width, height, this.bg)
}
 
var PATTERNS = [
  [2, 1, 2, 2, 2, 2, 0, 0], //
  [2, 2, 2, 1, 2, 2, 0, 0], //
  [2, 2, 2, 2, 2, 1, 0, 0], //
  [1, 2, 1, 2, 2, 3, 0, 0], //
  [1, 2, 1, 3, 2, 2, 0, 0], //
  [1, 3, 1, 2, 2, 2, 0, 0], //
  [1, 2, 2, 2, 1, 3, 0, 0], //
  [1, 2, 2, 3, 1, 2, 0, 0], //
  [1, 3, 2, 2, 1, 2, 0, 0], //
  [2, 2, 1, 2, 1, 3, 0, 0], //
  [2, 2, 1, 3, 1, 2, 0, 0], //
  [2, 3, 1, 2, 1, 2, 0, 0], //
  [1, 1, 2, 2, 3, 2, 0, 0], //
  [1, 2, 2, 1, 3, 2, 0, 0], //
  [1, 2, 2, 2, 3, 1, 0, 0], //
  [1, 1, 3, 2, 2, 2, 0, 0], //
  [1, 2, 3, 1, 2, 2, 0, 0], //
  [1, 2, 3, 2, 2, 1, 0, 0], //
  [2, 2, 3, 2, 1, 1, 0, 0], //
  [2, 2, 1, 1, 3, 2, 0, 0], //
  [2, 2, 1, 2, 3, 1, 0, 0], //
  [2, 1, 3, 2, 1, 2, 0, 0], //
  [2, 2, 3, 1, 1, 2, 0, 0], //
  [3, 1, 2, 1, 3, 1, 0, 0], //
  [3, 1, 1, 2, 2, 2, 0, 0], //
  [3, 2, 1, 1, 2, 2, 0, 0], //
  [3, 2, 1, 2, 2, 1, 0, 0], //
  [3, 1, 2, 2, 1, 2, 0, 0], //
  [3, 2, 2, 1, 1, 2, 0, 0], //
  [3, 2, 2, 2, 1, 1, 0, 0], //
  [2, 1, 2, 1, 2, 3, 0, 0], //
  [2, 1, 2, 3, 2, 1, 0, 0], //
  [2, 3, 2, 1, 2, 1, 0, 0], //
  [1, 1, 1, 3, 2, 3, 0, 0], //
  [1, 3, 1, 1, 2, 3, 0, 0], //
  [1, 3, 1, 3, 2, 1, 0, 0], //
  [1, 1, 2, 3, 1, 3, 0, 0], //
  [1, 3, 2, 1, 1, 3, 0, 0], //
  [1, 3, 2, 3, 1, 1, 0, 0], //
  [2, 1, 1, 3, 1, 3, 0, 0], //
  [2, 3, 1, 1, 1, 3, 0, 0], //
  [2, 3, 1, 3, 1, 1, 0, 0], //
  [1, 1, 2, 1, 3, 3, 0, 0], //
  [1, 1, 2, 3, 3, 1, 0, 0], //
  [1, 3, 2, 1, 3, 1, 0, 0], //
  [1, 1, 3, 1, 2, 3, 0, 0], //
  [1, 1, 3, 3, 2, 1, 0, 0], //
  [1, 3, 3, 1, 2, 1, 0, 0], //
  [3, 1, 3, 1, 2, 1, 0, 0], //
  [2, 1, 1, 3, 3, 1, 0, 0], //
  [2, 3, 1, 1, 3, 1, 0, 0], //
  [2, 1, 3, 1, 1, 3, 0, 0], //
  [2, 1, 3, 3, 1, 1, 0, 0], //
  [2, 1, 3, 1, 3, 1, 0, 0], //
  [3, 1, 1, 1, 2, 3, 0, 0], //
  [3, 1, 1, 3, 2, 1, 0, 0], //
  [3, 3, 1, 1, 2, 1, 0, 0], //
  [3, 1, 2, 1, 1, 3, 0, 0], //
  [3, 1, 2, 3, 1, 1, 0, 0], //
  [3, 3, 2, 1, 1, 1, 0, 0], //
  [3, 1, 4, 1, 1, 1, 0, 0], //
  [2, 2, 1, 4, 1, 1, 0, 0], //
  [4, 3, 1, 1, 1, 1, 0, 0], //
  [1, 1, 1, 2, 2, 4, 0, 0], //
  [1, 1, 1, 4, 2, 2, 0, 0], //
  [1, 2, 1, 1, 2, 4, 0, 0], //
  [1, 2, 1, 4, 2, 1, 0, 0], //
  [1, 4, 1, 1, 2, 2, 0, 0], //
  [1, 4, 1, 2, 2, 1, 0, 0], //
  [1, 1, 2, 2, 1, 4, 0, 0], //
  [1, 1, 2, 4, 1, 2, 0, 0], //
  [1, 2, 2, 1, 1, 4, 0, 0], //
  [1, 2, 2, 4, 1, 1, 0, 0], //
  [1, 4, 2, 1, 1, 2, 0, 0], //
  [1, 4, 2, 2, 1, 1, 0, 0], //
  [2, 4, 1, 2, 1, 1, 0, 0], //
  [2, 2, 1, 1, 1, 4, 0, 0], //
  [4, 1, 3, 1, 1, 1, 0, 0], //
  [2, 4, 1, 1, 1, 2, 0, 0], //
  [1, 3, 4, 1, 1, 1, 0, 0], //
  [1, 1, 1, 2, 4, 2, 0, 0], //
  [1, 2, 1, 1, 4, 2, 0, 0], //
  [1, 2, 1, 2, 4, 1, 0, 0], //
  [1, 1, 4, 2, 1, 2, 0, 0], //
  [1, 2, 4, 1, 1, 2, 0, 0], //
  [1, 2, 4, 2, 1, 1, 0, 0], //
  [4, 1, 1, 2, 1, 2, 0, 0], //
  [4, 2, 1, 1, 1, 2, 0, 0], //
  [4, 2, 1, 2, 1, 1, 0, 0], //
  [2, 1, 2, 1, 4, 1, 0, 0], //
  [2, 1, 4, 1, 2, 1, 0, 0], //
  [4, 1, 2, 1, 2, 1, 0, 0], //
  [1, 1, 1, 1, 4, 3, 0, 0], //
  [1, 1, 1, 3, 4, 1, 0, 0], //
  [1, 3, 1, 1, 4, 1, 0, 0], //
  [1, 1, 4, 1, 1, 3, 0, 0], //
  [1, 1, 4, 3, 1, 1, 0, 0], //
  [4, 1, 1, 1, 1, 3, 0, 0], //
  [4, 1, 1, 3, 1, 1, 0, 0], //
  [1, 1, 3, 1, 4, 1, 0, 0], //
  [1, 1, 4, 1, 3, 1, 0, 0], //
  [3, 1, 1, 1, 4, 1, 0, 0], //
  [4, 1, 1, 1, 3, 1, 0, 0], //
  [2, 1, 1, 4, 1, 2, 0, 0], //
  [2, 1, 1, 2, 1, 4, 0, 0], //
  [2, 1, 1, 2, 3, 2, 0, 0], //
  [2, 3, 3, 1, 1, 1, 2, 0] //
]

function convertlength (length) {
  return Math.round(wx.getSystemInfoSync().windowWidth * length / 750)
}
function barc (id, code, width, height) {
  code128(wx.createCanvasContext(id), code, convertlength(width), convertlength(height))
}
module.exports = {
  barcode: barc
}