//define(['jquery', 'bluebird', 'speex', 'pcmdata', 'bitstring'], factory);
//$, Promise, SpeexLib, PCMDataLib
/**
 * Usage:
 *   var fileList = ...; // FileList object
 *   var word = ...;     // word for lookup
 *   require(['mdict-parser', 'mdict-renderer'], function(MParser, MRenderer) {
 *      MParser(fileList).then(function(resources) {
 *         var mdict = MRenderer(resources),
 *             dict_desc = resources.description.mdx;
 *         mdict.lookup(word).then(function($content) {
 *            // use $content to display result
 *         });
 *      });
 *    });
 */
var MRenderer = (function () {
  var MIME = {
    'css': 'text/css',
    'img': 'image',
    'jpg': 'image/jpeg',
    'png': 'image/png',
    'spx': 'audio/x-speex',
    'wav': 'audio/wav',
    'mp3': 'audio/mp3',
    'js' : 'text/javascript'
  };
  
  function getExtension(filename, defaultExt) {
    return /(?:\.([^.]+))?$/.exec(filename)[1] || defaultExt;
  }
  
  // TODO: revoke unused resource, LRU
  // TODO: support for word variation
  return function createRenderer(resources) {

    var cache = (function createCache(mdd) {
    var repo = {};
    
      function get(id, load) {
        var entry = repo[id];
        if (!entry) {
          repo[id] = entry = new Promise(function(resolve) {
          var will = mdd.then(function(lookup) {
            console.log('lookup: ' + id);
            return lookup(id);
          }).then(load)
            .then(function(url) { resolve(url); });
          });
        }
        return entry;
      }
      
      return {get: get};
    })(resources['mdd']);
    
    function loadData(mime, data) {
      var blob = new Blob([data], {type: mime});
      return URL.createObjectURL(blob);      
    }
  
    function loadAudio(ext, data) {
      if (ext === 'spx') {
        var blob = decodeSpeex(String.fromCharCode.apply(null, data));
        return URL.createObjectURL(blob);
      } else {  // 'spx'
        return loadData(MIME[ext] || 'audio', data);
      }
    }
    
    // TODO: LRU cache: remove oldest one only after rendering.
    function replaceImage(index, img) {
      var $img = $(img);
      var src = $img.attr('src'), m = /^file:\/\/(.*)/.exec(src);
      if (m) { src = m[1]; }
      cache.get(src, loadData.bind(null, MIME['img']))
           .then(function(url) {
              $img.attr({src: url, src_: src});
            });
    }
    
    function playAudio(e, $a) {
      ($a || $(this)).find('audio')[0].play();
    }
    
    function renderAudio() {
      var $a = $(this);
      if ($a.attr('href_')) {
        playAudio($a);
      } else {
        var href = $a.attr('href'), res = href.substring(8);
        var ext = getExtension(res, 'wav');
        cache.get(res, loadAudio.bind(null, ext))
             .then(function(url) {
                $a.append($('<audio>').attr({src: url, src_: href})).on('click', playAudio);
                setTimeout(playAudio.bind($a));
             });
      }
      return false;
    }
    
    function replaceCss(index, link) {
      var $link = $(link);
      var href = $link.attr('href');
      cache.get(href, loadData.bind(null, MIME['css']))
           .then(function(url) {
              // $link.attr({href: url, href_: href});
              // TODO: Limit scope of embedded styles provide by mdd file
              // TODO: use shadow dom for Chrome
              // TODO: use scoped style for Firefox
              $link.replaceWith($('<style scoped>', {src_: href}).text('@import url("' + url + '")'));
            });
    }
    
    function injectJS(index, el) {
      var $el = $(el);
      var src = $el.attr('src');
      cache.get(src, loadData.bind(null, MIME['js']))
           .then(function(url) {
              $el.remove();
              $.ajax({url: url, dataType: 'script', cache: true});
            });
    }    
    
    function decodeSpeex(file) {
      var ogg = new Ogg(file, {file: true});
      ogg.demux();

      var header = Speex.parseHeader(ogg.frames[0]);
      console.log(header);

      var comment = new SpeexComment(ogg.frames[1]);
      console.log(comment.data);

      var spx = new Speex({
        quality: 8,
        mode: header.mode,
        rate: header.rate
      });
      
      var waveData = PCMData.encode({
          sampleRate: header.rate,
          channelCount: header.nb_channels,
          bytesPerSample: 2,
          data: spx.decode(ogg.bitstream(), ogg.segments)
        });

      return new Blob([Speex.util.str2ab(waveData)], {type: "audio/wav"});
    }

    function render($content) {
      if (resources['mdd']) {
        $content.find('img[src]').each(replaceImage);
        
        $content.find('link[rel=stylesheet]').each(replaceCss);
        
        $content.find('script[src]').each(injectJS);
        
        $content.find('a[href^="sound://"]').on('click', renderAudio);
        
        setTimeout(function() { $('#definition *').trigger('resize'); });
      }
      
// resolve entry:// link dynamically in mdict.js      
//      // rewrite in-page link
//      $content.find('a[href^="entry://"]').each(function() { 
//        var $el = $(this), href = $el.attr('href');
//        if (href.match('#')) {
//          $el.attr('href', href.substring(8));
//        }
//      });
      
      return $content;
    }    
    
    return {
      lookup: function lookup(query) {
        return (resources['mdx'] || resources['mdd'])
          .then(function (lookup) {
            return lookup(query);
          }).then(function (definitions) {
            console.log('lookup done!');
            var html = definitions.reduce(function(prev, txt) { 
              return prev + '<p></p>' + txt;
            }, '<p>' + definitions.length + ' entry(ies) </p>');
            return Promise.resolve(render($('<div>').html(html)));
          });
      },
      
      search: function (query) {
        return resources['mdx'].then(function(lookup) {
          return lookup(query);
        });
      },
      
      render: render,
    };
  }

}());
