/**
	 * Get tag dropdown select
	 * @param int $selected, selected level
	 * @return string
	 */
	public static function getTagMenu( $selected = '' ) {
		$s  = "<label for='wpRatingTag'>" . wfMsgHtml('readerfeedback-tagfilter') . "</label>&#160;";
		$s .= Xml::openElement( 'select', array('name' => 'ratingtag', 'id' => 'wpRatingTag') );
		foreach( ReaderFeedback::getFeedbackTags() as $tag => $weight ) {
			$s .= Xml::option( wfMsg( "readerfeedback-$tag" ), $tag, $selected===$tag );
		}
		$s .= Xml::closeElement('select')."\n";
		return $s;
	}
   	/**
	* Get a table of the vote totals for a page
	* @param Title $page
	* @param int $period, number of days back
	* @param array $add, optional vote to add on (used to visually avoid lag)
	* @param string $cache, optional param to not use cache
	* @return string HTML table
	*/	
	public static function getVoteAggregates(
		$page, $period, $add = array(), $cache = 'useCache'
	) {
		global $wgLang, $wgMemc;
		$votes = null;
		$now = time();
		$key = wfMemcKey( 'feedback', 'ratingtally', $page->getArticleId(), $period );
		// Check cache
		if( $cache == 'useCache' ) {
			$set = $wgMemc->get($key);
			// Cutoff is at the 24 hour mark due to the way the aggregate 
			// schema groups ratings by date for graphs.
			$cache_cutoff = $now - ($now % 86400);
			if( is_array($set) && count($set) == 2 ) {
				list($val,$time) = $set;
				$touched = wfTimestamp( TS_UNIX, RatingHistory::getTouched($page) );
				if( $time > $cache_cutoff && $time > $touched ) {
					$votes = $val;
				}
			}
		}
		// Do query, cache miss
		if( !isset($votes) ) {
			// Set cutoff time for period
			$dbr = wfGetDB( DB_SLAVE );
			$cutoff_unixtime = $now - ($period * 24 * 3600);
			// Use integral number of days to be consistent with graphs
			$cutoff_unixtime = $cutoff_unixtime - ($cutoff_unixtime % 86400);
			$cutoff = $dbr->addQuotes( $dbr->timestamp( $cutoff_unixtime ) );
			// Get the first revision possibly voted on in the range
			$firstRevTS = $dbr->selectField( 'revision',
				'rev_timestamp',
				array( 'rev_page' => $page->getArticleId(), "rev_timestamp <= $cutoff" ),
				__METHOD__,
				array( 'ORDER BY' => 'rev_timestamp DESC' )
			);
			// Find average, median...
			$res = $dbr->select( array( 'revision', 'reader_feedback' ),
				array( 'rfb_ratings' ),
				array( 'rev_page' => $page->getArticleId(),
					"rev_id = rfb_rev_id",
					"rfb_timestamp >= $cutoff",
					// Trigger INDEX usage
					"rev_timestamp >= ".$dbr->addQuotes($firstRevTS) ),
				__METHOD__,
				array( 'USE INDEX' => array('revision' => 'page_timestamp') )
			);
			$votes = array();
			foreach( ReaderFeedback::getFeedbackTags() as $tag => $w ) {
				$votes[$tag] = array( 0 => 0, 1 => 0, 2 => 0, 3 => 0, 4 => 0 );
			}
			// Read votes and tally the numbers
			foreach ( $res as $row ) {
				$dims = ReaderFeedback::expandRatings( $row->rfb_ratings );
				foreach( $dims as $tag => $val ) {
					if( isset($votes[$tag]) && isset($votes[$tag][$val]) ) {
						$votes[$tag][$val]++;
					}
				}
			}
			// Tack on $add for display (used to avoid cache/lag)
			foreach( $add as $tag => $val ) {
				if( isset($votes[$tag]) && isset($votes[$tag][$val]) ) {
					$votes[$tag][$val]++;
				}
			}
			$wgMemc->set( $key, array( $votes, $now ), 24*3600 );
		}
		// Output multi-column list
		$html = "<table class='rfb-reader_feedback_table' cellspacing='0'><tr>";
		foreach( ReaderFeedback::getFeedbackTags() as $tag => $w ) {
			// Get tag average...
			$dist = isset($votes[$tag]) ? $votes[$tag] : array();
			$count = array_sum($dist);
			if( $count ) {
				$ave = ($dist[0] + 2*$dist[1] + 3*$dist[2] + 4*$dist[3] + 5*$dist[4])/$count;
				$ave = round($ave,1);
			} else {
				$ave = '-'; // DIV by zero
			}
			$html .= '<td align="center"><b>'.wfMsgHtml("readerfeedback-$tag").'</b>&#160;&#160;'.
				'<sup>('.wfMsgHtml('ratinghistory-ave',$wgLang->formatNum($ave)).')</sup></td>';
		}
		$html .= '</tr><tr>';
		foreach( $votes as $dist ) {
			$html .= '<td><table>';
			$html .= '<tr><th align="left">'.wfMsgHtml('ratinghistory-table-rating').'</th>';
			for( $i = 1; $i <= 5; $i++ ) {
				$html .= "<td align='center' class='rfb-rating-option-".($i-1)."'>$i</td>";
			}
			$html .= '</tr><tr>';
			$html .= '<th align="left">'.wfMsgHtml("ratinghistory-table-votes").'</th>';
			$html .= '<td align="center">'.$dist[0].'</td>';
			$html .= '<td align="center">'.$dist[1].'</td>';
			$html .= '<td align="center">'.$dist[2].'</td>';
			$html .= '<td align="center">'.$dist[3].'</td>';
			$html .= '<td align="center">'.$dist[4].'</td>';
			$html .= "</tr></table></td>\n";
		}
		$html .= '</tr></table>';
		return $html;
	}
	public static function addRatingLink( &$skintemplate, &$nav_urls, &$oldid, &$revid ) {
		# Add rating tab
		if( $skintemplate->getTitle() && ReaderFeedback::isPageRateable( $skintemplate->getTitle() ) ) {
			$nav_urls['ratinghist'] = array( 
				'text' => wfMsg( 'ratinghistory-link' ),
				'href' => $skintemplate->makeSpecialUrl( 'RatingHistory', 
					"target=" . wfUrlencode( "{$skintemplate->thispage}" ) )
			);
		}
		return true;
	}
	private function submit() {
		global $wgUser;
		$dbw = wfGetDB( DB_MASTER );
		# Get date timestamp...
		$now = wfTimestampNow();
		$date = str_pad( substr( $now, 0, 8 ), 14, '0' );
		if( count($this->dims) == 0 )
			return self::REVIEW_ERROR;
		$ratings = $this->flattenRatings( $this->dims );
		# Make sure revision is valid!
		$rev = Revision::newFromId( $this->oldid );
		if( !$rev || !$rev->getTitle()->equals( $this->page ) ) {
			return self::REVIEW_ERROR; // opps!
		}
		$ip = wfGetIP();
		if( !$wgUser->getId() && !$ip ) {
			return self::REVIEW_ERROR; // we need to keep track somehow
		}
		$article = new Article( $this->page );
		# Check if the user is spamming reviews...
		if( $wgUser->pingLimiter( 'feedback' ) || $wgUser->pingLimiter() ) {
			return self::REVIEW_ERROR;
		}
		# Check if user already voted before...
		if( self::userAlreadyVoted( $this->page, $this->oldid ) ) {
			return self::REVIEW_DUP;
		}
		# Update review records to limit double voting!
		$insertRow = array( 
			'rfb_rev_id'    => $this->oldid,
			'rfb_user'      => $wgUser->getId(),
			'rfb_ip'        => $ip,
			'rfb_timestamp' => $dbw->timestamp( $now ),
			'rfb_ratings'   => $ratings
		);
		# Make sure initial page data is there to begin with...
		$insertRows = array();
		foreach( $this->dims as $tag => $val ) {
			if( $val < 0 ); // don't store "unsure" votes
			$insertRows[] = array(
				'rfh_page_id' => $rev->getPage(),
				'rfh_tag'     => $tag,
				'rfh_total'   => 0,
				'rfh_count'   => 0,
				'rfh_date'    => $date
			);
		}
		$dbw->insert( 'reader_feedback', $insertRow, __METHOD__, 'IGNORE' );
		$dbw->insert( 'reader_feedback_history', $insertRows, __METHOD__, 'IGNORE' );
		# Update aggregate data for this page over time...
		$touched = $dbw->timestamp( $now );
		$insertRows = array();
		foreach( $this->dims as $tag => $val ) {
			if( $val < 0 ) continue; // don't store "unsure" votes
			# Update daily averages
			$dbw->update( 'reader_feedback_history',
				array( 'rfh_total = rfh_total + '.intval($val), 
					'rfh_count = rfh_count + 1'),
				array( 'rfh_page_id' => $rev->getPage(), 
					'rfh_tag' => $tag,
					'rfh_date' => $date ),
				__METHOD__
			);
			# Get effective tag values for this page..
			list($aveVal,$n) = ReaderFeedback::getAverageRating( $article, $tag, true );
			$insertRows[] = array( 
				'rfp_page_id' => $rev->getPage(),
				'rfp_tag'     => $tag,
				'rfp_ave_val' => $aveVal,
				'rfp_count'   => $n,
				'rfp_touched' => $touched
			);
		}
		# Update recent averages
		$dbw->replace( 'reader_feedback_pages', array( 'PRIMARY' ), $insertRows, __METHOD__ );
		# For logged in users, box should disappear
		if( $wgUser->getId() ) {
			$this->page->invalidateCache();
		}
		# Prune expired page aggregate data
		if( 0 == mt_rand( 0, 99 ) ) {
			ReaderFeedback::purgeExpiredAverages();
		}
		return self::REVIEW_OK;
	}
	public function purgePage() {
		global $wgUploadDirectory;
		foreach( ReaderFeedback::getFeedbackTags() as $tag => $weight ) {
			$dir = "{$wgUploadDirectory}/graphs/".$this->page->getArticleId()."/{$tag}/";
			if( is_dir( $dir ) ) {
				$handle = opendir( $dir );
				if( $handle ) {
					while( false !== ( $file = readdir($handle) ) ) {
						@unlink("$dir/$file");
					}
					closedir( $handle );
				}
			}
			@rmdir( $dir );
		}
		return true;
	}
	function getQueryInfo() {
		$conds = $this->mConds;
		$conds[] = 'rfp_page_id = page_id';
		$conds['rfp_tag'] = $this->tag;
		$conds['page_namespace'] = $this->namespace;
		// Has to be good/crappy enough
		switch( $this->tier ) {
			case 3: $conds[] = "rfp_ave_val > 3"; break;
			case 1: $conds[] = "rfp_ave_val < 2"; break;
			default: $conds[] = "rfp_ave_val >= 2 AND rfp_ave_val <= 3"; break;
		}
		// Reasonable samples only
		$conds[] = 'rfp_count >= '.$this->mDb->addQuotes( ReaderFeedback::getFeedbackSize() );
		return array(
			'tables'  => array('reader_feedback_pages','page'),
			'fields'  => 'page_namespace,page_title,page_len,rfp_ave_val,rfp_count',
			'conds'   => $conds,
			'options' => array( 'USE INDEX' => array('reader_feedback_pages' => 'rfp_tag_val_page') )
		);
	}