/*
* copyright 2010-2012 Ryan Day
* http://github.com/soldair/node-qrcode
*
* Licensed under the MIT license:
*   http://www.opensource.org/licenses/mit-license.php
*
* canvas example and fallback support example provided by Joshua Koo
*	http://jabtunes.com/labs/qrcode.html
*	"Instant QRCode Mashup by Joshua Koo!"
*	as far as i can tell the page and the code on the page are public domain 
*	
* original table example and library provided by Kazuhiko Arase
*	http://d-project.googlecode.com/svn/trunk/misc/qrcode/js/
*
*/

var bops = require('bops')
var QRCodeLib = require('./qrcode.js');
var QRVersionCapacityTable = require('./qrcapacitytable.js').QRCapacityTable;
var QRCode = QRCodeLib.QRCode;

exports.QRCodeDraw = QRCodeDraw;
exports.QRVersionCapacityTable = QRVersionCapacityTable;
exports.QRErrorCorrectLevel = QRCodeLib.QRErrorCorrectLevel;
exports.QRCode = QRCodeLib.QRCode;

function QRCodeDraw(){}

QRCodeDraw.prototype = {
  scale:4,//4 px module size
  defaultMargin:20,
  marginScaleFactor:5,
  // you may configure the error behavior for input string too long
  errorBehavior:{
    length:'trim'
  },
  color:{
    dark:'black',
    light:'white'
  },
  defaultErrorCorrectLevel:QRCodeLib.QRErrorCorrectLevel.H,
  QRErrorCorrectLevel:QRCodeLib.QRErrorCorrectLevel,
  draw:function(canvas,text,options,cb){

    var level,
    error,
    errorCorrectLevel;
    
    var args = Array.prototype.slice.call(arguments);
    cb = args.pop(); 
    canvas = args.shift();
    text = args.shift();
    options = args.shift()||{};

    
    if(typeof cb != 'function') {
      //enforce callback api just in case the processing can be made async in the future
      // or support proc open to libqrencode
      throw new Error('callback required');
    }
    
    if(typeof options !== "object"){
      options.errorCorrectLevel = options;
    }
    

    this.QRVersion(
      text
      ,options.errorCorrectLevel||this.QRErrorCorrectLevel.H
      ,options.version
    ,function(e,t,l,ec){

      text = t,level = l,error = e,errorCorrectLevel = ec;
    });

    this.scale = options.scale||this.scale;
    this.margin = typeof(options.margin) === 'undefined' ? this.defaultMargin : options.margin;
    
    if(!level) {
      //if we are unable to find an appropriate qr level error out
      cb(error,canvas);
      return;
    }

    //create qrcode!
    try{
      
      var qr = new QRCodeLib.QRCode(level, errorCorrectLevel)
      , scale = this.scale||4
      , ctx = canvas.getContext('2d')
      , width = 0;

      qr.addData(text);
      qr.make();

      var margin = this.marginWidth();
      var currenty = margin;
      width = this.dataWidth(qr)+ margin*2;
      
      this.resetCanvas(canvas,ctx,width);

      for (var r = 0,rl=qr.getModuleCount(); r < rl; r++) {
        var currentx = margin;
        for (var c = 0,cl=qr.getModuleCount(); c < cl; c++) {
          if (qr.isDark(r, c) ) {
            ctx.fillStyle = this.color.dark;
            ctx.fillRect (currentx, currenty, scale, scale);
          } else if(this.color.light){
            //if falsy configured color
            ctx.fillStyle = this.color.light;
            ctx.fillRect (currentx, currenty, scale, scale);
          }
          currentx += scale;
        }
        currenty += scale;
      }
    } catch (e) {
      error = e;
    }
    
    cb(error,canvas,width);    
  },
  drawBitArray:function(text/*,errorCorrectLevel,options,cb*/) {

    var args = Array.prototype.slice.call(arguments),
      cb = args.pop(),
      text = args.shift(),
      options = args.shift() || {};

    //argument processing
    if(typeof cb != 'function') {
      //enforce callback api just in case the processing can be made async in the future
      // or support proc open to libqrencode
      throw new Error('callback required as last argument');
    }

    //this interface kinda sucks - there is very small likelyhood of this ever being async
    this.QRVersion(text,options.errorCorrectLevel,options.version,function(e,t,l,ec){
      text = t,level = l,error = e,errorCorrectLevel = ec;
    });

    if(!level) {
      //if we are unable to find an appropriate qr level error out
      cb(error,[],0);
      return;
    }

    //create qrcode!
    try{

      var qr = new QRCodeLib.QRCode(level, errorCorrectLevel)
      , scale = this.scale||4
      , width = 0,bits,bitc=0,currenty=0;
      
      qr.addData(text);
      qr.make();
      
      width = this.dataWidth(qr,1);
      bits = new Array(width*width);

      
      for (var r = 0,rl=qr.getModuleCount(); r < rl; r++) {
        for (var c = 0,cl=qr.getModuleCount(); c < cl; c++) {
          if (qr.isDark(r, c) ) {
            bits[bitc] = 1;
          } else {
            bits[bitc] = 0;
          }
          bitc++;
        }
      }
    } catch (e) {
      error = e;
      console.log(e.stack);
    }
    
    cb(error,bits,width);
  },
  QRVersion:function(text,errorCorrectLevel,version,cb){
    var c = bops.from(text).length,// BINARY LENGTH!
        error,
        errorCorrectLevel = this.QRErrorCorrectLevel[errorCorrectLevel];
        
        
        if(errorCorrectLevel === undefined) errorCorrectLevel = this.defaultErrorCorrectLevel

    var errorCorrectIndex = [1,0,3,2],//fix odd mapping to order in table
        keys = ['L','M','Q','H'],
        capacity = 0,
        versionSpecified = false;
        
    if(typeof version !== "undefined" && version !== null) {
      versionSpecified = true;
    }
    //TODO ADD THROW FOR INVALID errorCorrectLevel...?
    
    if(versionSpecified){
      //console.log('SPECIFIED VERSION! ',version);
      //i have specified a version. this will give me a fixed size qr code. version must be valid. 1-40
      capacity = QRVersionCapacityTable[version][errorCorrectIndex[errorCorrectLevel]];
      
    } else {
      //figure out what version can hold the amount of text
      for(var i=0,j=QRVersionCapacityTable.length;i<j;i++) {
        capacity = QRVersionCapacityTable[i][errorCorrectIndex[errorCorrectLevel]];
        if(c < QRVersionCapacityTable[i][errorCorrectIndex[errorCorrectLevel]]){
          version = i+1;
          break;
        }
      }
      //if not version set to max
      if(!version) {
        version = QRVersionCapacityTable.length-1;
      }
    }
    
    if(capacity < c){
      if(this.errorBehavior.length == 'trim'){
        text = text.substr(0,capacity);
        level = QRVersionCapacityTable.length; 
      } else {
        error = new Error('input string too long for error correction '
          +keys[errorCorrectIndex[errorCorrectLevel]]
          +' max length '
          + capacity
          +' for qrcode version '+version
        );
      }
    }
  
    if(cb) {
      cb(error,text,version,errorCorrectLevel);
    }
    return version;
  },
  marginWidth:function(){
    var margin = this.margin;
    this.scale = this.scale||4;
    //elegant white space next to code is required by spec
    if ((this.scale * this.marginScaleFactor > margin) && margin > 0){
      margin = this.scale * this.marginScaleFactor;
    }
    return margin;
  },
  dataWidth:function(qr,scale){
    return qr.getModuleCount()*(scale||this.scale||4);
  },
  resetCanvas:function(canvas,ctx,width){
    ctx.clearRect(0,0,canvas.width,canvas.height);
    if(!canvas.style) canvas.style = {};
    canvas.style.height = canvas.height = width;//square!
    canvas.style.width = canvas.width = width;
    
    if(this.color.light){
      ctx.fillStyle = this.color.light; 
      ctx.fillRect(0,0,canvas.width,canvas.height);
    } else {
      //support transparent backgrounds?
      //not exactly to spec but i really would like someone to be able to add a background with heavily reduced luminosity for simple branding
      //i could just ditch this because you could also just set #******00 as the color =P
      ctx.clearRect(0,0,canvas.width,canvas.height);
    }
  }
};