function Roti() {
  var rotiurl = 'http://roti.me/roti-0.1/';

  //dereived from V 1.00.A Beta
  //http://www.openjs.com/scripts/dom/css_selector/
  this.$ = function (all_selectors, root) {
    var selected = new Array();
    if(!document.getElementsByTagName) return selected;
    all_selectors = all_selectors.replace(/\s*([^\w])\s*/g,"$1");//Remove the 'beutification' spaces
    var selectors = all_selectors.split(",");
    // Grab all of the tagName elements within current context
    var getElements = function(context,tag) {
      if (!tag) tag = '*';
      // Get elements matching tag, filter them for class selector
      var found = new Array;
      for (var a=0,len=context.length; con=context[a],a<len; a++) {
        var eles;
        if (tag == '*') eles = con.all ? con.all : con.getElementsByTagName("*");
        else eles = con.getElementsByTagName(tag);

        for(var b=0,leng=eles.length;b<leng; b++) found.push(eles[b]);
      }
      return found;
    }

      COMMA:
      for(var i=0,len1=selectors.length; selector=selectors[i],i<len1; i++) {
        if (! root) root = document;
        var context = new Array(root);
        var inheriters = selector.split(" ");

          SPACE:
          for(var j=0,len2=inheriters.length; element=inheriters[j],j<len2;j++) {
            //This part is to make sure that it is not part of a CSS3 Selector
            var left_bracket = element.indexOf("[");
            var right_bracket = element.indexOf("]");
            var pos = element.indexOf("#");//ID
            if(pos+1 && !(pos>left_bracket&&pos<right_bracket)) {
              var parts = element.split("#");
              var tag = parts[0];
              var id = parts[1];
              var ele = document.getElementById(id);
              if(!ele || (tag && ele.nodeName.toLowerCase() != tag)) { //Specified element not found
                continue COMMA;
              }
              context = new Array(ele);
              continue SPACE;
            }

            pos = element.indexOf(".");//Class
            if(pos+1 && !(pos>left_bracket&&pos<right_bracket)) {
              var parts = element.split('.');
              var tag = parts[0];
              var class_name = parts[1];

              var found = getElements(context,tag);
              context = new Array;
              var rx = new RegExp('(^|\\s)'+class_name+'(\\s|$)')
              for (var l=0,len=found.length; fnd=found[l],l<len; l++) {
                if(fnd.className && fnd.className.match(rx)) context.push(fnd);
              }
              continue SPACE;
            }

            if(element.indexOf('[')+1) {//If the char '[' appears, that means it needs CSS 3 parsing
              // Code to deal with attribute selectors
              if (element.match(/^(\w*)\[(\w+)([=~\|\^\$\*]?)=?['"]?([^\]'"]*)['"]?\]$/)) {
                var tag = RegExp.$1;
                var attr = RegExp.$2;
                var operator = RegExp.$3;
                var value = RegExp.$4;
              }
              var found = getElements(context,tag);
              context = new Array;
              var rx1 = new RegExp('(^|\\s)'+value+'(\\s|$)');
              var rx2 = new RegExp('^'+value+'-?');
              for (var l=0,len=found.length; fnd=found[l],l<len; l++) {
                if(operator=='=' && fnd.getAttribute(attr) != value) continue;
                if(operator=='~' && !fnd.getAttribute(attr).match(rx1)) continue;
                if(operator=='|' && !fnd.getAttribute(attr).match(rx2)) continue;
                if(operator=='^' && fnd.getAttribute(attr).indexOf(value)!=0) continue;
                if(operator=='$' && fnd.getAttribute(attr).lastIndexOf(value)!=(fnd.getAttribute(attr).length-value.length)) continue;
                if(operator=='*' && !(fnd.getAttribute(attr).indexOf(value)+1)) continue;
                else if(!fnd.getAttribute(attr)) continue;
                context.push(fnd);
              }

              continue SPACE;
            }

            //Tag selectors - no class or id specified.
            var found = getElements(context,element);
            context = found;
          }
        for (var o=0,len=context.length;o<len; o++) selected.push(context[o]);
      }
    return selected;
  };

  this.spec = {};

  this.process =  function() {
    if (this.sidebar) {
      this.processSideBar();
    } else {
      this.processInnerLinks();
    }
  };

  this.processInnerLinks = function() {
    var links = this.findRotilinks();
    for (var i = 0; i < links.length; i++) {
      var rotilink = links[i];
      var root = this.getEntryRoot(rotilink);
      this.extractAndSearch(rotilink, root);
    }
  }

  this.processSideBar = function() {
    var roots = this.genericFind(document, this.spec.root);
    var rotilink = this.findRotilinks()[0];

    if (roots[0] && roots.length == 1) {
      this.extractAndSearch(rotilink, roots[0]);
    } else { // not found any root or found more than one root
      var tags = this.getTags(document);
      var url = this.genRequestUrl(document.location, null, tags, null);
      this.callSearch(rotilink, url);
    }
  }

  this.getStrippedText = function(elem) {
    if (elem == null) return '';
    var str;
    if (elem.textContent) {
      str = elem.textContent;
    } else
    if (elem.innerText) {
      str = elem.innerText;
    } else {
      str = elem.innerHTML.replace(/<[^<]+>/g, '');
    }
    return str.replace(/(&nbsp;|&#8208;|&#8210;|&ndash;|&#8211;|&mdash;|&#8212;|&#8213;|&#8275)/g, ' ')
      .replace(/(&shy;|&#xAD;|&#173;|&#x200B;|&#8203)/g,'')
      .replace(/[\u200b\u200d\u00ad]/g, '')
      .replace(/[!\?;:"'`“”^\*\+\-_\(\)\[\]\{\}#@&~\.,\\\/\|]/g, ' ')
      .replace(/\s+/g, ' ');
  }
  
  this.extractAndSearch = function(rotilink, root) {
    var title = this.getTitle(root);
    var tags = this.getTags(root);
    var link = this.getLink(root);
    var body = this.getBody(root);
    var info = this.getInfo(root);

    body = this.getStrippedText(body);
    //console.log(body);
    info = this.getStrippedText(info);
    //console.log(info);
    body = body.replace(info, '');
    //console.log(body);
    
    var len = body.length;
    if (len > 300) {
      var bodytop = body.substring(0, 199); // 199 chars
      var bodybottom = body.substring(len-100, len); // 100 chars
      //console.log(bodytop);
      //console.log(bodybottom);
      body = bodytop + ' ' + bodybottom;
      //console.log(body);
    }
    var url = this.genRequestUrl(link, title, tags, body);
    this.callSearch(rotilink, url);
  }

  this.genRequestUrl = function(link, title, tags, body) {
    var tmp = [];
    tmp.push(rotiurl + 'search/jsonp');
    tmp.push('?url=');
    tmp.push(encodeURI(link));
    if (tags.length > 0) {
      tmp.push('&tags=');
      tmp.push(encodeURI(tags.join(',')));
    }
    if (title) {
      tmp.push('&title=');
      tmp.push(encodeURI(title));
    }
    if (body) {
      tmp.push('&content=');
      tmp.push(encodeURI(body));
    }

    return tmp.join('');
  }

  this.findRotilinks = function() {
    return this.$('div.rotilinks');
  }
  // find parent node that has class property that match given string
  this.findParent = function(className, elm) {
    var re = new RegExp("(^|\\s)" + className + "(\\s|$)");
    do {
      elm = elm.parentNode;
    } while (elm && elm.nodeType == 1 && ! re.test(elm.getAttribute('class')));
    return elm;
  }

  this.getEntryRoot = function(elm) {
    return this.findParent(this.spec.root, elm);
  }

  this.genericFind = function(root, spec) {
    for (var i=0; i<spec.length; i++) {
      var tmp = this.$(spec[i], root);
      if (tmp.length > 0) {
        return tmp;
      }
    }
    return [null];
  }

  this.getTitle = function(root) {
    var t = this.genericFind(root, this.spec.title)[0];
    if (t != null)
      return this.getStrippedText(t);
    else
      return '';
  }

  this.getBody = function(root) {
    var b = this.genericFind(root, this.spec.body)[0];
    if (b != null)
      return b;
    else
      return document.createElement('p');
  }

  this.getInfo = function(root) {
    var i = this.genericFind(root, this.spec.info)[0];
    return i;
  }

  this.getTags = function(root) {
    var tags = this.genericFind(root, this.spec.tags)
    var ret = [];
    for (var i = 0; i < tags.length; i++) {
      if (tags[i] && tags[i].innerHTML.search(/uncategorize/i) < 0) ret.push(tags[i].innerHTML);
    }
    return ret;
  }

  this.getLink = function(root) {
    var tmp = this.genericFind(root, this.spec.link)
    if (tmp[0]) {
      return tmp[0].href;
    }
    return document.location;
  }

  this.cnt = 0;

  this.callSearch = function(elm, url) {
    //console.log(url);
    var callback = this.genCallBack(elm);
    url = url + '&callback=' + callback;
    var script = document.createElement('script');
    script.setAttribute('src', url);
    script.setAttribute('type', "text/javascript");
    document.body.appendChild(script);
  }

  this.genCallBack = function(elm) {
    var callback = function(result) {
      _roti.render(elm, result.data);
    }
    var name = '_roti_callback' + this.cnt;
    this.cnt++;
    window[name] = callback;
    return name;
  }

  this.render = function(elm, entries) {
    if (elm != null && entries != null) {
      var len = entries.length;

      if (len > 0) {
        var ul = document.createElement('ul');
        for (var i=0; i<len; i++) {
          var li = document.createElement('li');
          var a = document.createElement('a');
          a.setAttribute('href',entries[i].link);
          a.innerHTML = entries[i].title;
          li.appendChild(a);
          ul.appendChild(li);
        }
        elm.appendChild(ul);
      } else {
        var p = document.createElement('p');
        p.innerHTML = 'no content found';
        elm.appendChild(p);
      }
    } else if (elm != null) {
      var pp = document.createElement('p');
      pp.innerHTML = 'no content found';
      elm.appendChild(pp);
    }
  }
}

function RotiBlogspot()  {
  this.name = 'blogspot';
  this.spec.root = 'post';
  this.spec.title = ['*.post-title a'];
  this.spec.body = ['div.post-body', 'div.entry-content'];
  this.spec.tags = ["a[rel='tag']"];
  this.spec.link = ['a.timestamp-link'];
  this.spec.info = ['div.post-footer'];
}

function RotiWordpress()  {
  this.name = 'wordpress';
  this.spec.root = 'post';
  this.spec.title = ["a[rel='bookmark']"];
  this.spec.body = ['div.post-content', 'div.entry', 'div.article'];
  this.spec.tags = ["a[rel='category','*.postmetadata a']"];
  this.spec.link = ["a[rel='bookmark']"];
  this.spec.info = ['div.postinfo', 'div.post-info'];
}

function RotiExteen() {
  this.name = 'exteen';
  this.spec.root = 'entry';
  this.spec.title = ['div.title a'];
  this.spec.body = ['div.post'];
  this.spec.tags = ['span.tag a'];
  this.spec.link = ['div.title a'];
  this.spec.info = ['div.info'];
}

function RotiDrupal() {
  this.name = 'drupal';
  this.spec.root = 'node';
  this.spec.title = ["a[rel='bookmark', '*.title a']"];
  this.spec.body = ['div.entry-content', 'div.content'];
  this.spec.tags = ["a[rel='tag']"];
  this.spec.link = ["a[rel='bookmark', '*.title a']"];
  this.spec.info = ['div.meta'];
}

_roti = new Roti();
RotiBlogspot.prototype = _roti;
RotiWordpress.prototype = _roti;
RotiExteen.prototype = _roti;
RotiDrupal.prototype = _roti;

//------------------------------------
// addLoadListener
function addLoadListener(fn){
    if(typeof window.addEventListener !='undefined') window.addEventListener('load',fn,false);
    else if(typeof document.addEventListener !='undefined') document.addEventListener('load',fn,false);
    else if(typeof window.attachEvent !='undefined') window.attachEvent('onload',fn);
    else{
        var oldfn=window.onload
        if(typeof window.onload !='function') window.onload=fn;
        else window.onload=function(){oldfn();fn();}
    }
}

addLoadListener(function() {
  var engine =  null;
  var gen = _roti.$("meta[name='generator']");
  var url = document.location.toString();

  if (gen && gen.length > 0) {
    var c = gen[0].content;
    if (c.search(/wordpress/i) >= 0) {
      engine = new RotiWordpress();
    } else if (c.search(/blogger/i) >= 0) {
      engine = new RotiBlogspot();
    } else if (c.search(/exteen/i) >= 0) {
      engine = new RotiExteen();
    }
  }

  if (engine == null && url.search(/exteen\.com/i) >= 0) {
    engine = new RotiExteen();
  }

  if (engine == null) {
    var script = _roti.$("script[type='text/javascript']");
    if (script) {
      for (var a=0,len=script.length; engine==null && a<len; a++) {
        if (script[a].src.search(/drupal\.js/) >= 0) {
          engine = new RotiDrupal();
        }
      }
    }
  }

  if (engine == null && _roti.$('*.node').length > 0) {
    engine = new RotiDrupal();
  }

  //console.log("engine.name = ", engine.name); // FireBug

  engine.process();
});
