// wrapper for company data.
var Company = Class.create();
Company.prototype = {
  initialize: function(id, name, url) {
    this.id = id;
    this.name = name;
    this.url = url;
    this.domain = Helper.domain(url);
    this.rdomain = Helper.reverseDomain(this.domain);
  },
  
  toString: function() {
    return this.domain;
  }
};

// generic wrapper for graph drawing
Graph = Class.create();
Graph.prototype = {
  initialize: function(elementId, graphtype) {
    this.elementId = elementId;
    this.graphtype = graphtype;
  },
  
  // replaces the given DOM element's content with a graph
  draw: function(companies) {
    if (companies && companies.length > 0) {
      var e = $(this.elementId);
      if (e) {
        var g = this.graphtype.generate(companies);
        if (g) {
          while (e.firstChild) e.removeChild(e.firstChild);
          e.appendChild(g);
        }
      }
    }
  },
  
  dependentOnCompetitors: function() { return this.graphtype.dependentOnCompetitors(); }
};

// creates a browser-dependent flash object
Flash = {
  generate: function(movie, flashVars, id, width, height) {
    if (window.DetectFlashVer && DetectFlashVer(9, 0, 45)) {
      var acArgs = [
        'codebase', 'http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=9,0,45,0',
        'width', width, 'height', height, 'scale', 'noscale', 'salign', 'TL', 'wmode', 'opaque',
        'movie', movie, 'src', movie, 'FlashVars', flashVars, 'id', id, 'name', id, 'menu', 'true',
        'allowFullScreen', 'false', 'allowScriptAccess','sameDomain', 'quality', 'high', 'align', 'middle',
        'pluginspage', 'http://www.macromedia.com/go/getflashplayer', 'play', 'true', 'devicefont', 'false'
      ]

      var a = AC_GetArgs(acArgs, '.swf', 'movie', 'clsid:d27cdb6e-ae6d-11cf-96b8-444553540000', 'application/x-shockwave-flash');
      var str = '';
      
      if (isIE && isWin && !isOpera) {
        str += '<object ';
        for (var i in a.objAttrs) str += i + '="' + a.objAttrs[i] + '" ';
        str += '>';
        for (var i in a.params) str += '<param name="' + i + '" value="' + a.params[i] + '" /> ';
        str += '</object>';
      } else {
        str += '<embed ';
        for (var i in a.embedAttrs) str += i + '="' + a.embedAttrs[i] + '" ';
        str += '> </embed>';
      }
      
      var span = document.createElement('span');
      span.innerHTML = str;
      span.flash = true;
      return span;
    } else {
      var span = document.createElement('span');
      span.innerHTML = 'This content requires the <a href=http://www.macromedia.com/go/getflash/>Adobe Flash Player &raquo;</a>';
      return span;
    }
  }
};

// declare a few intermediate classes to assist with naming conventions
GraphType = { News: {}, Traffic: {}, Analytics: {}, SEO: {}, Finance: {} };

// base class for linked image graphs
GraphType.Image = Class.create();
GraphType.Image.prototype = {
  initialize: function(id, width, height, className) {
    this.id = id;
    this.width = width || 658;
    this.height = height || 268;
    this.className = className;
  },
  
  toString: function() { return this.id; },
  
  generate: function(companies) {
    if (!companies || companies.length == 0) return null;
    
    var img = this.imageUrl(companies);
    var link = this.linkUrl(companies);
    
    if (!img) return null;
    
    var i = document.createElement('img');
    i.width = this.width;
    i.height = this.height;
    i.className = this.className;
    i.src = img;
    
    if (!link) return i;
    
    var a = document.createElement('a');
    a.href = link;
    a.target = this.className ? 'bizshark_' + this.className : 'bizshark_external';
    a.appendChild(i);
    return a;
  },
  
  // override these
  imageUrl: function(companies) { return ''; },
  linkUrl:  function(companies) { return ''; },
  dependentOnCompetitors: function() { return true; }
};

// base class for iframe embedded graph
GraphType.Iframe = Class.create();
GraphType.Iframe.prototype = {
  initialize: function(id, width, height, className) {
    this.id = id;
    this.width = width || 658;
    this.height = height || 268;
    this.className = className;
  },
  
  toString: function() { return this.id; },
  
  generate: function(companies) {
    if (!companies || companies.length == 0) return null;
    
    var url = this.iframeUrl(companies);
    if (!url) return null;
    
    var iframe = document.createElement('iframe');
    iframe.src = url;
    iframe.width = this.width;
    iframe.height = this.height;
    iframe.className = this.className;
    return iframe;
  },
  
  // override these
  iframeUrl: function(companies) { return ''; },
  dependentOnCompetitors: function() { return true; }
};

// base class for maani xml/swf graphs
GraphType.Maani = Class.create();
GraphType.Maani.prototype = {
  initialize: function(id, width, height) {
    this.id = id;
    this.width = width || 658;
    this.height = height || 268;
  },
  
  toString: function() { return this.id; },
  
  // override these
  dependentOnCompetitors: function() { return true; },
  xmlUrl: function(companies) { return ''; },
  
  // this only changes the status bar. actual link is part of flash graph
  linkUrl: function(companies) { return ''; },
  
  generate: function(companies) {
    var id = this.id + new Date().getTime();
    var flashVars = 'chart_id=' + id + '&library_path=/flash/&xml_source=' + encodeURIComponent(this.xmlUrl(companies));
    var span = Flash.generate('/flash/charts', flashVars, id, this.width, this.height);
    
    var link = this.linkUrl(companies);
    if (link && span.flash) {
      var a = document.createElement('a');
      a.href = link;
      a.appendChild(span);
      return a;
    } else {
      return span;
    }
  }
};

// base class for amMap charts
GraphType.AmMap = Class.create();
GraphType.AmMap.prototype = {
  initialize: function(id, width, height) {
    this.id = id;
    this.width = width || 658;
    this.height = height || 268;
  },
  
  toString: function() { return this.id; },
  
  // override these
  dependentOnCompetitors: function() { return true; },
  settingsUrl: function(companies) { return ''; },
  xmlUrl: function(companies) { return ''; },
  
  generate: function(companies) {
    var id = this.id + new Date().getTime();
    var xml = encodeURIComponent(this.xmlUrl(companies));
    var settings = encodeURIComponent(this.settingsUrl(companies));
    var flashVars = 'path=/flash/ammap/&settings_file=' + settings + '&data_file=' + xml;
    
    return Flash.generate('/flash/ammap', flashVars, id, this.width, this.height);
  }
};

// technorati news
GraphType.News.Technorati = new GraphType.Image('Technorati', 640, 268, 'technorati');
Object.extend(GraphType.News.Technorati, {
  imageUrl: function(companies) { return '/graph/news_technorati/' + Helper.companyIds(companies).join('_') + '.png'; },
  linkUrl:  function(companies) { return 'http://technorati.com/chart/"' + companies[0].domain + '"' }
});

// news sources bar graph - linear scale
GraphType.News.LinksLinear = new GraphType.Maani('LinksLinear', 640, 268);
Object.extend(GraphType.News.LinksLinear, {
  xmlUrl: function(companies) {
    return '/graph/news_details?t=' + ($('news_tabs') ? 1 : 0) + '&c=' + Helper.companyIds(companies).join(',');
  },
  linkUrl: function(companies) { return $('news_tabs') ? '' : window.location.href + '/news'; },
  onclick: function(companyId, infoSourceTypeId) { News.section(companyId, infoSourceTypeId); }
});

// news sources bar graph - logarithmic scale
GraphType.News.LinksLogarithmic = new GraphType.Maani('LinksLogarithmic', 640, 268);
Object.extend(GraphType.News.LinksLogarithmic, {
  xmlUrl: function(companies) {
    return '/graph/news_details?l=1&t=' + ($('news_tabs') ? 1 : 0) + '&c=' + Helper.companyIds(companies).join(',');
  },
  linkUrl: function(companies) { return $('news_tabs') ? '' : window.location.href + '/news'; },
  onclick: function(companyId, infoSourceTypeId) { News.section(companyId, infoSourceTypeId); }
});

// page views heat map
GraphType.Traffic.MapPageViews = new GraphType.AmMap('MapPageViews', 640, 300);
Object.extend(GraphType.Traffic.MapPageViews, {
  dependentOnCompetitors: function() { return false; },
  settingsUrl: function(companies) { return '/graph/traffic_map_settings'; },
  xmlUrl: function(companies) { return '/graph/traffic_map?c=' + Helper.companyIds(companies).join(','); }
});

// rank heat map
GraphType.Traffic.MapRank = new GraphType.AmMap('MapRank', 640, 300);
Object.extend(GraphType.Traffic.MapRank, {
  dependentOnCompetitors: function() { return false; },
  settingsUrl: function(companies) { return '/graph/traffic_map_settings'; },
  xmlUrl: function(companies) { return '/graph/traffic_map?r=1&c=' + Helper.companyIds(companies).join(','); }
});

// quantcast traffic
GraphType.Traffic.Quantcast = new GraphType.Image('Quantcast', 563, 375, 'quantcast');
Object.extend(GraphType.Traffic.Quantcast, {
  imageUrl: function(companies) {
    var p = '';
    for (var i = 0; i < companies.length; i++)
      if (companies[i].rdomain)
        p += (p ? '%20' : '') + 'wd:' + companies[i].rdomain + '|' + i;
    
    return 'http://www.quantcast.com/livegraph.png?gt=mwg&dty=pp&dtr=dd&wunit=' + p + '&c=1';
  },
  
  linkUrl: function(companies) {
    var c = '';
    for (var i = 0; i < companies.length; i++)
      if (companies[i].rdomain)
        c += (c ? '&' : '') + 'domain' + i + '=' + companies[i].domain;
    
    return 'http://www.quantcast.com/profile/traffic-compare?' + c;
  }
});

// alexa traffic
GraphType.Traffic.Alexa = new GraphType.Iframe('Alexa', 400, 268, 'alexa');
Object.extend(GraphType.Traffic.Alexa, {
  iframeUrl: function(companies) { return '/graph/traffic_alexa?c=' + companies.join(','); }
});

// compete traffic
GraphType.Traffic.Compete = new GraphType.Image('Compete', 658, 268, 'compete');
Object.extend(GraphType.Traffic.Compete, {
  imageUrl: function(companies) { return 'http://grapher.compete.com/' + companies.join('+') + '_uv.png'; },
  linkUrl:  function(companies) { return 'http://siteanalytics.compete.com/' + companies.join('+') + '/?metric=uv'; }
});

// compete visits
GraphType.Traffic.CompeteVisits = new GraphType.Image('CompeteVisits', 658, 268, 'compete');
Object.extend(GraphType.Traffic.CompeteVisits, {
  imageUrl: function(companies) { return 'http://grapher.compete.com/' + companies.join('+') + '_sess.png'; },
  linkUrl:  function(companies) { return 'http://siteanalytics.compete.com/' + companies.join('+') + '/?metric=sess'; }
});

// compete average-stay
GraphType.Traffic.CompeteAverageStay = new GraphType.Image('CompeteAverageStay', 658, 268, 'compete');
Object.extend(GraphType.Traffic.CompeteAverageStay, {
  imageUrl: function(companies) { return 'http://grapher.compete.com/' + companies.join('+') + '_avgStay.png'; },
  linkUrl:  function(companies) { return 'http://siteanalytics.compete.com/' + companies.join('+') + '/?metric=avgStay'; }
});

// compete pages-per-visit
GraphType.Traffic.CompetePagesPerVisit = new GraphType.Image('CompetePagesPerVisit', 658, 268, 'compete');
Object.extend(GraphType.Traffic.CompetePagesPerVisit, {
  imageUrl: function(companies) { return 'http://grapher.compete.com/' + companies.join('+') + '_ppv.png'; },
  linkUrl:  function(companies) { return 'http://siteanalytics.compete.com/' + companies.join('+') + '/?metric=ppv'; }
});

// quantcast frequency
GraphType.Traffic.Quantcast.Frequency = new GraphType.Image('Frequency', 255, 156, 'frequency');
Object.extend(GraphType.Traffic.Quantcast.Frequency, {
  imageUrl: function(companies) { return 'http://www.quantcast.com/profile/pieGraph?wunit=wd%3A' + companies[0].rdomain; },
  linkUrl:  function(companies) { return 'http://www.quantcast.com/' + companies[0] + '/traffic'; },
  
  generate: function(companies) {
    if (!companies || companies.length == 0) return null;
    
    var freq = document.createElement('div');
    freq.className = 'frequency frequency_' + companies.length;
    
    var title = document.createElement('span');
    title.className = 'frequency_title';
    title.innerHTML = 'Traffic Frequency';
    freq.appendChild(title);
    
    var source = document.createElement('a');
    source.className = 'frequency_source';
    source.href = 'http://www.quantcast.com/';
    source.target = 'bizshark_frequency';
    freq.appendChild(source);
    
    var inner = document.createElement('div');
    inner.className = 'frequency_images frequency_images_' + companies.length;
    freq.appendChild(inner);
    
    // nest multiple graphs in one div, use css to hide parts of the images
    for (var i = 0; i < companies.length; i++) {
      var d = document.createElement('div');
      d.appendChild(GraphType.Image.prototype.generate.call(this, [companies[i]]));
      inner.appendChild(d);
    }
    
    return freq;
  }
});

// quantcast demographics
GraphType.Traffic.Quantcast.Demographics = new GraphType.Image('Demographics', 260, 430, 'demographics_image');
Object.extend(GraphType.Traffic.Quantcast.Demographics, {
  imageUrl: function(companies) { return 'http://www.quantcast.com/profile/demographicGraphAll?wunit=wd%3A' + companies[0].rdomain; },
  linkUrl:  function(companies) { return 'http://www.quantcast.com/' + companies[0] + '/demographics'; },
  
  generate: function(companies) {
    if (!companies || companies.length == 0) return null;
    
    var demo = document.createElement('div');
    demo.className = 'demographics demographics_' + companies.length;
    
    var title = document.createElement('span');
    title.className = 'demographics_title';
    title.innerHTML = 'Demographics';
    demo.appendChild(title);
    
    var source = document.createElement('a');
    source.className = 'demographics_source';
    source.href = 'http://www.quantcast.com/';
    source.target = 'bizshark_demographics';
    demo.appendChild(source);
    
    var domains = document.createElement('div');
    domains.className = 'demographics_domains demographics_domains_' + companies.length;
    demo.appendChild(domains);
    
    for (var i = 0; i < companies.length; i++) {
      var d = document.createElement('div');
      d.className = 'demographics_domain_' + (i + 1);
      d.innerHTML = companies[i].domain;
      domains.appendChild(d);
    }
    
    var inner = document.createElement('div');
    inner.className = 'demographics_images demographics_images_' + companies.length;
    demo.appendChild(inner);
    
    // nest multiple graphs in one div, use css to hide parts of the images
    for (var i = 0; i < companies.length; i++) {
      var d = document.createElement('div');
      d.appendChild(GraphType.Image.prototype.generate.call(this, [companies[i]]));
      inner.appendChild(d);
    }
    
    return demo;
  }
});

// polar graph of multiple performance metrics
GraphType.Analytics.PerformanceMatrix = new GraphType.Maani('PerformanceMatrix', 640, 200);
Object.extend(GraphType.Analytics.PerformanceMatrix, {
  xmlUrl: function(companies) {
    return '/graph/analytics_performance?c=' + Competitors.currentCompanyId() + '&s=' + Helper.companyIds(companies).join(',');
  }
});

// pie chart of market share for selected analytics metric
GraphType.Analytics.MarketShare = new GraphType.Maani('MarketShare', 640, 240);
Object.extend(GraphType.Analytics.MarketShare, {
  dependentOnCompetitors: function() { return false; },
  xmlUrl: function(companies) {
    var s = $('select_share');
    var c = Competitors.currentCompanyId();
    return '/graph/analytics_share?c=' + c + '&s=' + (s ? s.value : '');
  }
});

// compare any two analytics metrics
GraphType.Analytics.MarketSegmentation = new GraphType.Maani('MarketSegmentation', 640, 328);
Object.extend(GraphType.Analytics.MarketSegmentation, {
  dependentOnCompetitors: function() { return false; },
  xmlUrl: function(companies) {
    var x = $('segmentation_x');
    var y = $('segmentation_y');
    var z = $('segmentation_z');
    var c = Competitors.currentCompanyId();
    return '/graph/analytics_segmentation?c=' + c + '&x=' + (x ? x.value : '') + '&y=' + (y ? y.value : '') + '&z=' + (z ? z.value : '');
  }
});

// polar graph of company performance for popular keywords
GraphType.SEO.Paid = new GraphType.Maani('Paid', 640, 210);
Object.extend(GraphType.SEO.Paid, {
  dependentOnCompetitors: function() { return false; },
  xmlUrl: function(companies) {
    return '/graph/seo_performance?t=0&c=' + Competitors.currentCompanyId();
  }
});

// polar graph of company performance for popular keywords
GraphType.SEO.Organic = new GraphType.Maani('Organic', 640, 210);
Object.extend(GraphType.SEO.Organic, {
  dependentOnCompetitors: function() { return false; },
  xmlUrl: function(companies) {
    return '/graph/seo_performance?t=1&c=' + Competitors.currentCompanyId();
  }
});

// polar graph of company performance for popular keywords
GraphType.SEO.Traffic = new GraphType.Maani('Traffic', 640, 210);
Object.extend(GraphType.SEO.Traffic, {
  dependentOnCompetitors: function() { return false; },
  xmlUrl: function(companies) {
    return '/graph/seo_performance?t=2&c=' + Competitors.currentCompanyId();
  }
});

// financial revenue vs traffic regression
GraphType.Finance.RevenueTraffic = new GraphType.Maani('RevenueTraffic', 640, 268);
Object.extend(GraphType.Finance.RevenueTraffic, {
  dependentOnCompetitors: function() { return false; },
  xmlUrl: function(companies) {
    return '/graph/revenue_traffic?c=' + Competitors.currentCompanyId();
  }
});

// financial revenue vs employee count regression
GraphType.Finance.RevenueEmployees = new GraphType.Maani('RevenueEmployees', 640, 268);
Object.extend(GraphType.Finance.RevenueEmployees, {
  dependentOnCompetitors: function() { return false; },
  xmlUrl: function(companies) {
    return '/graph/revenue_employees?c=' + Competitors.currentCompanyId();
  }
});



// customize auto-complete display
AutoComplete.BizShark = Class.create();
Object.extend(Object.extend(AutoComplete.BizShark.prototype, AutoComplete.Remote.prototype), {
  handleResponse: function(request, query) {
    var values = null;
    try { values = eval(request.responseText); } catch (e) {}
    if (!values || !(values instanceof Array)) return false;
    
    this.dataArray = [];
    this.displayArray = [];
    
    var q = query.match(/^www\.(.+)/i);
    if (q) query = q[1];
    var r = new RegExp('^(' + query.strip() + ')(.*)$', 'i');
    
    for (var i = 0; i < values.length; i++) {
      var m = null;
      var display = '';
      var name = values[i][0];
      var url  = values[i][1];
      
      if (url && (m = url.match(/^http:\/\/(www\.)?(.+)/i))) url = m[2];
      
      if (m = name.match(r)) {
        display = '<b>' + m[1] + '</b>' + m[2] + '<span>' + url + '</span>';
        
      } else if (m = url.match(r)) {
        display = name + '<span>' + '<b>' + m[1] + '</b>' + m[2] + '</span>';
        
      } else {
        display = name + '<span>' + url + '</span>';
      }
      
      this.dataArray.push(url);
      this.displayArray.push(display);
    }
    
    return true;
  }
});

// hardcode some size adjustments because of special search bar styling
AutoComplete.HomeSearch = Class.create();
Object.extend(Object.extend(AutoComplete.HomeSearch.prototype, AutoComplete.BizShark.prototype), {
  getY:  function(elem) { return AutoComplete.Remote.prototype.getY.call(this,  elem) - 2; },
  width: function(elem) { return AutoComplete.Remote.prototype.width.call(this, elem) - 10; }
});

// hardcode some size adjustments because of special search bar styling
AutoComplete.HeaderSearch = Class.create();
Object.extend(Object.extend(AutoComplete.HeaderSearch.prototype, AutoComplete.BizShark.prototype), {
  getX:  function(elem) { return AutoComplete.Remote.prototype.getX.call(this,  elem) - 1; },
  getY:  function(elem) { return AutoComplete.Remote.prototype.getY.call(this,  elem) - 7; },
  width: function(elem) { return AutoComplete.Remote.prototype.width.call(this, elem) - 11; }
});

// hardcode some size adjustments because of special search bar styling
AutoComplete.AddCompetitor = Class.create();
Object.extend(Object.extend(AutoComplete.AddCompetitor.prototype, AutoComplete.BizShark.prototype), {
  getX:  function(elem) { return AutoComplete.Remote.prototype.getX.call(this,  elem) + 1; },
  getY:  function(elem) { return AutoComplete.Remote.prototype.getY.call(this,  elem) - 1; },
  width: function(elem) { return AutoComplete.Remote.prototype.width.call(this, elem) - 13; }
});

// miscellaneous helper methods
Helper = {
  domain: function(url) {
    if (!url || !url.length || url.length < 1) return null;
    var m = url.match(/^(http:\/\/)?(www\.)?([^\/]+)/i);
    return (m && m[3]) ? m[3] : '';
  },
  
  reverseDomain: function(domain) {
    if (!domain || !domain.length || domain.length < 1) return null;
    return domain.split(/\./).reverse().join('.');
  },
  
  companyIds: function(companies) {
    if (!companies) return null;
    
    var c = [];
    for (var i = 0; i < companies.length; i++) c.push(companies[i].id);
    return c;
  }
};




/**
 * Management interfaces for controlling UI interaction and state data including:
 *  - list of selected competitors
 *  - multiple graphs and graph types
 *  - selected news tab
 */

Home = {
  selectTab: function(tab) {
    if (tab == this.selectedTab) return;
    
    // hide the current tab content
    if (this.selectedTab) {
      var link = $('home_' + this.selectedTab + '_link');
      if (link) link.className = null;
      hide('home_' + this.selectedTab);
      new Ajax.Request('/state/hometab', { parameters: 'state=' + tab });
    }
    
    // show the new tab content
    var link = $('home_' + tab + '_link');
    if (link) link.className = 'selected';
    show('home_' + tab);
    
    this.selectedTab = tab;
  },
  
  removeBookmark: function(companyId, bookmarkId, bookmarkName) {
    var c = confirm('Remove bookmark for ' + bookmarkName + '?');
    if (!c) return;
    
    new Ajax.Request('/ajax/bookmark', { asynchronous: true, parameters: 'company_id=' + companyId });
    
    // remove the bookmark
    var tr = $('bookmark_' + bookmarkId);
    if (tr) tr.parentNode.removeChild(tr);
    
    // shift paginated bookmarks
    var page = parseInt(tr.getAttribute('page'));
    var table = $('bookmarks_' + page);
    
    if (table) {
      while (nextTable = $('bookmarks_' + ++page)) {
        var trs = nextTable.getElementsByTagName('tr');
        var del = (trs.length == 1);
        
        // shift one bookmark up a page
        if (trs.length > 0) {
          var nextTr = trs[0];
          nextTr.parentNode.removeChild(nextTr);
          table.appendChild(nextTr);
          nextTr.setAttribute('page', page - 1);
        }
        
        // if the last page becomes empty, remove the table/page link
        if (del) {
          nextTable.parentNode.removeChild(nextTable);
          var p = $('bookmarks_page_' + page);
          if (p) p.parentNode.removeChild(p);
        }
        
        table = nextTable;
      }
    }
    
    // remove pagination if less than 2 pages
    if (!$('bookmarks_1')) {
      var pages = $('bookmarks_pages');
      if (pages) pages.parentNode.removeChild(pages);
    }
    
    // if all bookmarks deleted, show "no bookmarks" message
    var t = $('bookmarks_0');
    if (!t || t.getElementsByTagName('tr').length == 0) {
      if (t) t.parentNode.removeChild(t);
      show('nobookmarks');
    }
  }
}

Competitors = {
  companies: [],  // array of Companies
  selected: [],   // array of Companies
  
  // the first company in the list is the current company
  currentCompanyId: function() {
    return this.companies[0].id;
  },
  
  // returns whether the given company is selected
  isSelected: function(companyId) {
    if (!companyId || companyId <= 0) return false;
    
    for (var i = 0; i < this.selected.length; i++)
      if (this.selected[i].id == companyId)
        return true;
    
    return false;
  },
  
  // toggle selected state of a company - called by the UI
  toggle: function(companyId) {
    if (this.isSelected(companyId))
      this.deselect(companyId);
    else
      this.select(companyId);
  },
  
  // add a company - called by the UI when building the list
  add: function(company, selected) {
    if (company && company.id) {
      this.companies.push(company);
      var div = $('competitor_' + company.id);
      
      if (selected) {
        this.selected.push(company);
        if (div) div.className = 'competitor competitor_selected';
      } else {
        if (div) div.className = 'competitor';
      }
      
      return company;
    }
    
    return null;
  },
  
  // select an unselected competitor
  select: function(companyId) {
    var found = false;
    
    for (var i = 0; i < this.companies.length; i++) {
      if (this.companies[i].id == companyId) {
        if (this.selected.length >= 5) {
          var last = this.selected.pop();
          $('competitor_' + last.id).className = 'competitor';
        }
        
        this.selected.push(this.companies[i]);
        found = true;
        break;
      }
    }
    
    if (found) {
      $('competitor_' + companyId).className = 'competitor competitor_selected';
      Graphs.refresh();
      this.updateSelected();
      News.regenerateTabs();
    }
  },
  
  // deselect a selected competitor
  deselect: function(companyId) {
    var found = false;
    
    for (var i = 0; i < this.selected.length; i++) {
      if (this.selected[i].id == companyId) {
        this.selected.splice(i, 1);
        found = true;
        break;
      }
    }
    
    if (found) {
      $('competitor_' + companyId).className = 'competitor';
      Graphs.refresh();
      this.updateSelected();
      News.regenerateTabs();
    }
  },
  
  // save selected competitors state
  updateSelected: function() {
    var s = [];
    for (var i = 0; i < this.selected.length; i++) s.push(this.selected[i].id);
    new Ajax.Request('/state/competitors', { asynchronous: false, parameters: 'state=' + s.join(',') });
  },
  
  // toggle display of the "show x more competitors section"
  showMore: function() {
    show('competitors_list_more');
    hide('competitors_more');
    show('competitors_less');
    new Ajax.Request('/state/more_competitors', { asynchronous: false, parameters: 'state=1' });
  },
  
  // toggle display of the "show x more competitors section"
  showLess: function() {
    hide('competitors_list_more');
    hide('competitors_less');
    show('competitors_more');
    new Ajax.Request('/state/more_competitors', { asynchronous: false });
  },
  
  // reload the competitor list markup
  reload: function() {
    new Ajax.Updater('competitors_td', '/competitors', {
      asynchronous: true,
      evalScripts: true,
      parameters: 'company_id=' + this.currentCompanyId(),
      onComplete: function() {
        // scripts not evaluated synchronously by prototype library
        setTimeout(function() {
          setup_temporaries();
          Graphs.refresh();
          News.regenerateTabs();
        }, 50);
      }
    });
  },
  
  // add a custom competitor
  addCustom: function(query) {
    var r = new Ajax.Request('/ajax/add_competitor', {
      asynchronous: false,
      parameters: 'company_id=' + this.currentCompanyId() + '&q=' + query
    });
    
    if (r.transport.responseText) this.reload();
  },
  
  // delete a custom competitor
  removeCustom: function(companyId) {
    var r = new Ajax.Request('/ajax/remove_competitor', {
      asynchronous: false,
      parameters: 'company_id=' + this.currentCompanyId() + '&competitor_id=' + companyId
    });
    
    if (r.transport.responseText) this.reload();
  }
};

Graphs = {
  graphs: {},     // elementId -> Graph
  anchorIds: {},  // elementId -> anchorId
  states: {},     // stateVar  -> graphtype
  
  // redraw a graph, optionally changing the graph type
  update: function(elementId, graphtype) {
    if (elementId) {
      var g = this.graphs[elementId];
      if (!g || (graphtype && graphtype != g.graphtype))
        g = new Graph(elementId, graphtype);
      
      if (g) {
        this.graphs[elementId] = g;
        g.draw(Competitors.selected);
        if (BrowserDetect.browser == 'Explorer')
          $(elementId).parentNode.className = $(elementId).parentNode.className;
      }
    }
  },
  
  // change the graph type and corresponding UI elements
  select: function(elementId, graphtype, graphtabAnchorId, stateVar) {
    if (graphtabAnchorId) {
      var prevAnchor = $(this.anchorIds[elementId]);
      var anchor = $(graphtabAnchorId);
      
      if (prevAnchor) prevAnchor.parentNode.className = '';
      if (anchor) anchor.parentNode.className = 'graphtab_selected';
      
      this.anchorIds[elementId] = graphtabAnchorId;
    }
    
    this.update(elementId, graphtype);
    if (stateVar) {
      if (this.states[stateVar] && this.states[stateVar] != graphtype)
        new Ajax.Request('/state/' + stateVar, { asynchronous: false, parameters: 'state=' + graphtype });
      this.states[stateVar] = graphtype;
    }
  },
  
  // redraw all graphs after selected competitors have changed
  refresh: function() {
    for (elementId in this.graphs)
      if (this.graphs[elementId].dependentOnCompetitors())
        this.graphs[elementId].draw(Competitors.selected);
  }
};

Profile = {
  showFull: function() {
    Element.hide('overview_profile_show_more');
    
    if ($('overview_profile_summary_text_full')) {
      Element.hide('overview_profile_summary_text');
      Element.show('overview_profile_summary_text_full');
    } else {
      $('overview_profile_summary_text').style.background = 'none';
    }
    
    Element.show('overview_profile_detail');
  }
}

News = {
  selectedCompanyId: null,
  selectedinfoSourceTypeId: null,
  textHovered: false,
  pages: {}, // sourceType -> page
  counts: {}, // companyId -> newsCount
  
  page: function(direction, sourceType) {
    var page = this.pages[sourceType] || 0;
    var newpage = page + direction;
    var newpagediv = $('news_' + sourceType + '_' + newpage);
    
    if (newpagediv) {
      this.pages[sourceType] = newpage;
      $('news_page_' + sourceType).innerHTML = newpage + 1;
      hide('news_' + sourceType + '_' + page);
      show(newpagediv);
      page = newpage;
    }
    
    var prev = $('news_page_prev_' + sourceType);
    if (prev) {
      if (page == 0) {
        prev.className = 'news_page_disabled';
        prev.href = null;
      } else {
        prev.className = null;
        prev.href = '#';
      }
    }
    
    var next = $('news_page_next_' + sourceType);
    if (next) {
      if ($('news_' + sourceType + '_' + (page + 1))) {
        next.className = null;
        next.href = '#';
      } else {
        next.className = 'news_page_disabled';
        next.href = null;
      }
    }
  },
  
  tab: function(companyId, onComplete) {
    if (this.selectedCompanyId != companyId) {
      new Ajax.Updater('news_content', '/news_content', {
        asynchronous: true,
        parameters: 'company_id=' + companyId,
        onComplete: onComplete
      });
      
      var oldtab = $('news_tab_' + this.selectedCompanyId);
      var newtab = $('news_tab_' + companyId);
      if (oldtab) oldtab.className = oldtab.className.match('last') ? 'last' : '';
      if (newtab) newtab.className = newtab.className.match('last') ? 'news_tab_selected last' : 'news_tab_selected';
      
      this.selectedCompanyId = companyId;
    } else {
      if (onComplete && typeof onComplete == 'function') onComplete();
    }
  },
  
  section: function(companyId, infoSourceTypeId) {
    this.tab(companyId, function() {
      this.rx = this.gx = this.bx = 255;
      var news = $('news_' + this.selectedinfoSourceTypeId);
      if (news) news.style.background = 'none';
    
      this.selectedinfoSourceTypeId = infoSourceTypeId;
      Scroll.to('news_' + infoSourceTypeId, this.flashSection.bind(this));
    }.bind(this));
  },
  
  flashSection: function() {
    var news = $('news_' + this.selectedinfoSourceTypeId);
    var rule = getStylesheetRule('.news_section_selected');
    
    if (news && rule && rule.style && rule.style.backgroundColor) {
      var m = rule.style.backgroundColor.match(/rgb\((\d+), ?(\d+), ?(\d+)\)/i);
      if (m) {
        this.r = parseInt(m[1]);
        this.g = parseInt(m[2]);
        this.b = parseInt(m[3]);
      } else if (m = rule.style.backgroundColor.match(/#([a-z0-9]{2})([a-z0-9]{2})([a-z0-9]{2})/i)) {
        this.r = parseInt(m[1], 16);
        this.g = parseInt(m[2], 16);
        this.b = parseInt(m[3], 16);
      }
      
      news.style.background = '#' + this.r.toString(16) + this.g.toString(16) + this.b.toString(16);
      this.rx = (255 - this.r) / 20.0;
      this.gx = (255 - this.g) / 20.0;
      this.bx = (255 - this.b) / 20.0;
      setTimeout(this.fadeSection.bind(this), 500);
    }
  },
  
  fadeSection: function() {
    this.r = Math.round(this.r + this.rx);  if (this.r > 255) this.r = 255;
    this.g = Math.round(this.g + this.gx);  if (this.g > 255) this.g = 255;
    this.b = Math.round(this.b + this.bx);  if (this.b > 255) this.b = 255;
    
    var done = (this.r >= 255 && this.g >= 255 && this.b >= 255);
    var news = $('news_' + this.selectedinfoSourceTypeId);
    if (news) news.style.background = done ? 'none' : '#' + this.r.toString(16) + this.g.toString(16) + this.b.toString(16);
    
    if (!done) setTimeout(this.fadeSection.bind(this), 100);
  },
  
  regenerateTabs: function() {
    if (Competitors.selected.length > 0 && $('news_tabs')) {
      new Ajax.Updater('news_tabs', '/competitor/news_tabs', {
        asynchronous: true,
        evalScripts: true,
        parameters: 'company_id=' + Competitors.companies[0].id + '&selected=' + this.selectedCompanyId,
        onComplete: this.validateCurrentTab.bind(this)
      });
    }
  },
  
  validateCurrentTab: function() {
    var currentTabStillExists = false;
      
    for (var i = 0; i < Competitors.selected.length; i++) {
      if (Competitors.selected[i].id == this.selectedCompanyId) {
        currentTabStillExists = true;
        break;
      }
    }
    
    if (!currentTabStillExists) this.tab(Competitors.selected[0].id);
  },
  
  showHoverText: function(e, divId) {
    var h = $('news_hover');
    if (h) {
      if (divId && h.divId != divId) {
        var div = $(divId);
        if (div) {
          h.divId = divId;
          h.innerHTML = div.innerHTML;
          
          var p = div.parentNode;
          h.style.left = (Prototype.Browser.IE ? p.clientLeft + p.offsetLeft : p.offsetLeft) + 50 + 'px';
          h.style.top  = (Prototype.Browser.IE ? p.clientTop  + p.offsetTop  : p.offsetTop)  + 20 + 'px';
        }
      }
      
      show(h);
      this.textHovered = true;
    }
  },
  
  hideHoverText: function(e) {
    this.textHovered = false;
    setTimeout('News.delayedHideHoverText()', 50);
  },
  
  delayedHideHoverText: function() {
    if (!this.textHovered) hide('news_hover');
  }
};

Analytics = {
  xSegmentation: null,
  ySegmentation: null,
  zSegmentation: null,
  
  setSelection: function(xSegmentation, ySegmentation, zSegmentation, share) {
    $('select_share').value = share;
    
    var xSelect = $('segmentation_x');
    var ySelect = $('segmentation_y');
    var zSelect = $('segmentation_z');
    
    if (xSelect && ySelect && zSelect) {
      xSelect.value = xSegmentation;
      ySelect.value = ySegmentation;
      zSelect.value = zSegmentation;
      
      this.xSegmentation = xSegmentation;
      this.ySegmentation = ySegmentation;
      this.zSegmentation = zSegmentation;
      
      xSelect.options[ySelect.selectedIndex].disabled = true;
      xSelect.options[zSelect.selectedIndex].disabled = true;
      ySelect.options[xSelect.selectedIndex].disabled = true;
      ySelect.options[zSelect.selectedIndex].disabled = true;
      zSelect.options[xSelect.selectedIndex].disabled = true;
      zSelect.options[ySelect.selectedIndex].disabled = true;
      
      xSelect.options[ySelect.selectedIndex].className = 'disabled';
      xSelect.options[zSelect.selectedIndex].className = 'disabled';
      ySelect.options[xSelect.selectedIndex].className = 'disabled';
      ySelect.options[zSelect.selectedIndex].className = 'disabled';
      zSelect.options[xSelect.selectedIndex].className = 'disabled';
      zSelect.options[ySelect.selectedIndex].className = 'disabled';
    }
  },
  
  updateSegmentation: function() {
    var xSelect = $('segmentation_x');
    var ySelect = $('segmentation_y');
    var zSelect = $('segmentation_z');
    if (xSelect && ySelect && zSelect) {
      
      var xMetric = xSelect.value;
      var yMetric = ySelect.value;
      var zMetric = zSelect.value;
      if (xMetric && yMetric && zMetric && xMetric != yMetric && xMetric != zMetric && yMetric != zMetric) {
        
        Graphs.update('graph_segmentation');
        this.xSegmentation = xMetric;
        this.ySegmentation = yMetric;
        this.zSegmentation = zMetric;
        
        // disable mirror option. IE doesnt implement option.disabled
        for (var i = 0; i < xSelect.options.length; i++) {
          var d = (xSelect.options[i].value == yMetric || xSelect.options[i].value == zMetric);
          xSelect.options[i].disabled = d;
          xSelect.options[i].className = d ? 'disabled' : null;
        }
        for (var i = 0; i < ySelect.options.length; i++) {
          var d = (ySelect.options[i].value == xMetric || ySelect.options[i].value == zMetric);
          ySelect.options[i].disabled = d;
          ySelect.options[i].className = d ? 'disabled' : null;
        }
        for (var i = 0; i < zSelect.options.length; i++) {
          var d = (zSelect.options[i].value == xMetric || zSelect.options[i].value == yMetric);
          zSelect.options[i].disabled = d;
          zSelect.options[i].className = d ? 'disabled' : null;
        }
          
      } else {
        xSelect.value = this.xSegmentation;
        ySelect.value = this.ySegmentation;
        zSelect.value = this.zSegmentation;
      }
    }
  }
};

SEO = {
  showMore: function(type) {
    var table = $('keywords');
    
    if (table) {
      var trs = table.getElementsByTagName('tr');
      for (var i = 0; i < trs.length; i++) {
        var t = trs[i].getAttribute('type');
        if (t && parseInt(t) == type) show(trs[i]);
      }
    }
    
    hide('keywords_more_' + type);
  },
  
  showHistogramDetail: function(elem, companyName, sharedKeywords, sharedRanks, keyword, isRelevance, index) {
    var cn = $('histogram_detail_company');
    if (cn) cn.innerHTML = companyName;
    
    var box = $('histogram_detail_box');
    if (box) box.className = 'hd hd_' + index;
    
    var table = document.createElement('table');
    var tbody = document.createElement('tbody');
    var tr = document.createElement('tr');
    var th1 = document.createElement('th');
    var th2 = document.createElement('th');
    th1.innerHTML = 'Shared keywords&nbsp;&nbsp;&nbsp;';
    th2.innerHTML = (isRelevance ? 'Relevance' : 'Rank');
    th1.style.textAlign = 'left';
    th2.style.textAlign = 'right';
    table.width = '100%';
    tr.appendChild(th1);
    tr.appendChild(th2);
    tbody.appendChild(tr);
    table.appendChild(tbody);
    
    for (var i = 0; i < sharedKeywords.length; i++) {
      var tr = document.createElement('tr');
      var td1 = document.createElement('td');
      var td2 = document.createElement('td');
      if (sharedKeywords[i] == keyword) tr.className = 'selected';
      td1.innerHTML = sharedKeywords[i];
      td2.innerHTML = sharedRanks[i];
      td2.style.textAlign = 'right';
      tr.appendChild(td1);
      tr.appendChild(td2);
      tbody.appendChild(tr);
    }
    
    var sk = $('histogram_detail_keywords');
    while (sk.firstChild) sk.removeChild(sk.firstChild);
    sk.appendChild(table);
    
    var hd = $('histogram_detail');
    hd.parentNode.removeChild(hd);
    elem.appendChild(hd);
    show(hd);
  },
  
  hideHistogramDetail: function() {
    hide('histogram_detail');
  }
};

Contacts = {
  getContact: function(companyPersonId) {
    var r = new Ajax.Request('/ajax/get_contact', {
      asynchronous: false,
      parameters: 'company_id=' + Competitors.currentCompanyId() + '&person_id=' + companyPersonId
    });
    
    if (r.transport.responseText) {
      r = eval('(' + r.transport.responseText + ')');
      if (r.email) {
        var e = $('contact_email_' + companyPersonId);
        if (e) {
          e.innerHTML = r.email;
          e.className = 'contact_email';
          $('contact_' + companyPersonId).className = 'contact_owned';
        }
      }
      if (r.message) alert(r.message);
    }
  }
};

GoogleMaps = {
  maps: {},
  
  loadMap: function(divId, address) {
    var div = document.getElementById(divId);
    
    if (div) {
      if (GBrowserIsCompatible && GBrowserIsCompatible()) {
        var map = new GMap2(div);
        map.disableInfoWindow();
        map.enableScrollWheelZoom();
        map.addControl(new GSmallMapControl());
        this.maps[divId] = map;
      
        this.loadAddress(div, address);
      } else {
        div.innerHTML = '<br/><br/><br/><br/><center><i>your browser cannot<br/>display google maps</i></center>';
      }
    }
  },
  
  loadAddress: function(div, address) {
    if (address.length == 0) {
      div.innerHTML = '<br/><br/><br/><br/><br/><center><i>location not found</i></center>';
      return;
    }
    
    var a = address.join(', ');
    var geocoder = new GClientGeocoder();
    
    geocoder.getLatLng(a, function(point) {
      if (point) {
        var map = this.maps[div.id];
        map.setCenter(point, 10);
        var marker = new GMarker(point, {title: 'Click to see this location on Google Maps'});
        GEvent.addListener(marker, 'click', function() { window.open('http://maps.google.com/maps?q=' + a); });
        map.addOverlay(marker);
      } else {
        address.shift();
        this.loadAddress(div, address);
      }
    }.bind(GoogleMaps));
  }
};

Bookmark = {
  toggle: function() {
    $('title_flag').className ? this.remove() : this.add();
  },
  
  add: function() {
    $('title_flag').className = 'bookmarked';
    new Ajax.Request('/ajax/bookmark', { asynchronous: true, parameters: 'company_id=' + Competitors.currentCompanyId() + '&new=1' });
  },
  
  remove: function() {
    $('title_flag').className = null;
    new Ajax.Request('/ajax/bookmark', { asynchronous: true, parameters: 'company_id=' + Competitors.currentCompanyId() });
  }
};

// accelerated/decelerated scrollTo
Scroll = {
  scrolling: false,
  
  to: function(here, onComplete) {
    if (this.scrolling) return setTimeout(function() { Scroll.to(here); }, 50);
    this.scrolling = true;
    
    var scrollTo = 0;
    if (typeof here == 'number') {
      scrollTo = here;
    } else {
      var e = $(here);
      if (!e) return (this.scrolling = false);
      scrollTo = Element.top(e);
    }
    
    var max = document.body.scrollHeight - Viewport.height();
    if (scrollTo < 0) scrollTo = 0;
    if (scrollTo > max) scrollTo = max;
    this.start = Viewport.scrollTop();
    this.end = scrollTo;
    this.onComplete = onComplete;
    
    this.init();
    this.scroll();
  },
  
  // private
  scroll: function() {
    if (this.start == this.end) return this.done();
    
    var fraction = Math.pow((this.end - Viewport.scrollTop()) / (this.end - this.start), 2);
    var direction = this.start < this.end ? 1 : -1;
    var distance = (30 * (1 - Math.abs(fraction - .5))) * direction;
    Viewport.setScrollTop(Viewport.scrollTop() + distance);
    
    if ((direction == 1) ? (Viewport.scrollTop() < this.end) : (Viewport.scrollTop() > this.end))
      setTimeout(this.scroll.bind(this), 10);
    else
      this.done();
  },
  
  init: function() {
    var overlay = this.id ? $(this.id) : null;
    
    // overlaying an empty div prevents the browser from calculating hover effects, making scrolling smoother
    if (!overlay) {
      this.id = 'scroll_overlay';
      overlay = document.createElement('div');
      overlay.style.position = 'absolute';
      overlay.style.width    = '100%';
      overlay.style.height   = document.body.scrollHeight + 'px';
      overlay.style.top      = 0;
      overlay.style.left     = 0;
      overlay.style.zIndex   = 999999;
      overlay.id             = this.id;
      document.body.appendChild(overlay);
    }
    
    overlay.style.display  = '';
  },
  
  done: function() {
    $(this.id).style.display = 'none';
    this.scrolling = false;
    if (this.onComplete && typeof this.onComplete == 'function') this.onComplete();
  }
};

Viewport = {
  height: function() {
    if (document.compatMode == 'CSS1Compat') return document.documentElement.clientHeight;
    if (document.compatMode == 'BackCompat' && Prototype.Browser.IE) return document.body.clientHeight;
    return (window.innerHeight || document.body.clientHeight);
  },
  
  width: function() {
    if (document.compatMode == 'CSS1Compat') return document.documentElement.clientWidth;
    if (document.compatMode == 'BackCompat' && Prototype.Browser.IE) return document.body.clientWidth;
    return (window.innerWidth || document.body.clientWidth);
  },
  
  scrollTop: function() {
    return (window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop);
  },
  
  setScrollTop: function(scrollTop) {
    if (document.documentElement) document.documentElement.scrollTop = scrollTop;
    document.body.scrollTop = scrollTop;
  }
};

Page = {
  pages: {},
  heights: {},
  
  next: function(pageId) { this.delta(pageId,  1); },
  prev: function(pageId) { this.delta(pageId, -1); },
  delta: function(pageId, direction) {
    this.go(pageId, (this.pages[pageId] || 0) + direction);
  },
  
  go: function(pageId, newpage) {
    var page = this.pages[pageId] || 0;
    var newpagediv = $(pageId + '_' + newpage);
    
    if (newpagediv) {
      var pagediv = $(pageId + '_' + page);
      var pagenumdiv = $(pageId + '_page_' + page);
      if (pagenumdiv) pagenumdiv.className = null;
      if (pagediv) {
        if (!this.heights[pageId])
          this.heights[pageId] = pagediv.offsetHeight;
        hide(pagediv);
      }
      
      var pagenumdiv = $(pageId + '_page_' + newpage);
      if (pagenumdiv) pagenumdiv.className = 'selected';
      show(newpagediv);
      if (this.heights[pageId])
        newpagediv.style.height = this.heights[pageId] + 'px';
      
      this.pages[pageId] = page = newpage;
    }
    
    var prev = $(pageId + '_prev');
    if (prev) {
      if (page == 0) {
        prev.className = 'disabled';
        prev.href = null;
      } else {
        prev.className = null;
        prev.href = '#';
      }
    }
    
    var next = $(pageId + '_next');
    if (next) {
      if ($(pageId + '_page_' + (page + 1))) {
        next.className = null;
        next.href = '#';
      } else {
        next.className = 'disabled';
        next.href = null;
      }
    }
  }
};