<?php
require_once 'HTMLHelper.php';
require_once 'ModelHelper.php';
require_once 'StateHelper.php';
require_once 'GeocodingHelper.php';
require_once 'ApplicationConfig.php';

// App Configuration information
/**
 * The Config class holds application configuration information -- what kinds of
 * content the app is dealing with and other app-wide data like arrays for 
 * form choices, result page size, and so on. All of the configuration variables
 * should be declared as public static variables.
 *
 */
class Config extends ApplicationConfig {
    /**
     * The content type of the content objects that hold what's being reviewed
     *
     * @var string
     */
    public static $subjectType = 'Restaurant';
    /**
     * A human-readable description of the subject content object --
     * what we're reviewing. Displayed in various places in the app.
     *
     * @var string
     */
    public static $subjectDescription = 'Restaurant';
    /**
     * The content type of the content objects that hold reviews
     *
     * @var string
     */
    public static $reviewType  = 'RestaurantReview';
    /**
     * A human-readable description of the review content object. Displayed
     * in various places in the app.
     *
     * @var string
     */
    public static $reviewDescription = 'Review';
    /**
     * How many results per page to display
     *
     * @var integer
     */
    public static $pageSize    = 10;
    /**
     * Google Maps API Key, set in setup.php.
     *
     * @var string
     */
    public static $googleMapsAPIKey;    
    /**
     * A Google Maps URL representing the bounding box of the map on the front page.
     * Must contain "ll=". Set in setup.php. Defaults to the entire USA.
     *
     * @var string
     */
    public static $googleMapsInitialExtentURL = 'http://maps.google.com/maps?ll=37.439974,-95.185547&spn=49.491315,82.995117&hl=en';        
    /**
     * How lists on the front page can be sorted. Array keys are
     * used in a URL variable. Array values are what's displayed
     * to the user.
     *
     * @var array
     */
    public static $sortNames = array('rated' => 'Most Highly Rated',
    'reviewed' => 'Most Reviewed',
    'tagged' => 'Most Tagged',
    'added' => 'Most Recently Added',
    'city' => "By City",
    'cuisine' => "By Cuisine",
    'alpha' => "Alphabetical"
    );
    /**
     * How XN_Query knows how to sort the various options in $sortNames
     * Array keys are the same as $sortNames; array values are two element
     * arrays of (field, order)
     *
     * @var array
     */
    public static $sortFields = array('rated' => array('my->avgRating','desc'),
    'reviewed' => array('my->reviewCount','desc'),
    'tagged' => array('tagCount','desc'),
    'cuisine' => array('my->cuisine', 'asc'),
    'city' => array('my->city', 'asc'),
    'alpha' => array('title', 'asc'),
    'added' => array('createdDate','desc'));

    /**
     * The "container" attribute name that's used as a search parameter in
     * mapping and listing.
     *
     * @var string
     */
    public static $container = 'cuisine';
    /**
     * What are the choices for how many stars to give a subject when
     * entering a review? Array keys are what gets submitted with the
     * form; Array values are what is displayed to the user. If you change
     * this, update the validate_rating() method in the review's shape class
     *
     * @var array
     */
    public static $ratingChoices = array(
    5  => '5 stars',
    4  => '4 stars',
    3  => '3 stars',
    2  => '2 stars',
    1  => '1 star');

    /**
     * If stars aren't called stars (monkeys, points, cheese, etc.), set that
     * here. Be sure to change the images, too. (But not their filenames.)
     *
     * @var string
     */
    public static $starWord = 'star';
    /**
     * The name of the attribute of the subject in which reviews are stored
     *
     * @var string
     */
    public static $reviewAttribute = 'reviews';
    
    /**
     * Valid redirection destinations used by the _redirect.php template.
     * Array keys are what _redirect.php expects to see in the $target
     * variable; array values are the destination URLs
     */
    public static $redirectTargets = array('index' => 'index.php',
                                           'list'  => 'list.php',
                                           'subject' => 'subject.php');
    
    /**
     * Google Maps Map Type.
     * Choices are: 0 -- Street map
                    1 -- Satellite Map
                    2 -- Hybrid Map
     */ 
    public static $mapType = 0;
}

/**
 * This class describes the shape of the subjects. The name of the class
 * should be the same as the Config::$subjectType variable.
 *
 */
class Restaurant {
    /**
     * The name of the restaurant
     *
     * @var string
     */
    public $title;
    /**
     * Description is used as the restaurant's address
     *
     * @var string
     */
    public $description;
    /**
     * What cuisine does a restaurant serve?
     *
     * @var string
     */
    public $cuisine;
    /**
     * The city in which the restaurant is located e.g. Worcester
     *
     * @var string
     */
    public $city;    
    /**
     * The city in which the restaurant is located e.g. MA
     *
     * @var string
     */
    public $state;      
    /**
     * The restaurant's phone number
     *
     * @var string
     */
    public $phone;
    /**
     * The restaurant's latitude
     *
     * @var string
     */
    public $latitude;
    /**
     * The restaurant's longitude
     *
     * @var string
     */
    public $longitude;

    /**
     * Set rules for the fields. 
     *
     */
    public function __construct() {
        $this->_rules['title'] = array('required', array('maxlength', 100));
        $this->_labels['title'] = 'Restaurant Name'; 
        $this->_rules['description'] = array('required', array('maxlength', 100)); 
        $this->_labels['description'] = 'Address';         
        $this->_rules['city'] = array('required', array('maxlength', 100));
        // State is optional, because the address may be outside the USA. [Jon Aquino 2005-09-20]
        $this->_rules['cuisine'] = array('required', array('maxlength', 100));
        $this->_rules['_tags'] = array(array('maxlength', 100));
        $this->_labels['_tags'] = 'Tags';

        $this->_labels['phone'] = 'Phone Number';
        $this->_rules['phone'] = array(array('maxlength',15));
    }
    
    public function validate_state($state) {        
        if ($state == '') { return null; } 
        if (StateHelper::validStateAbbreviation($state)) { return null; }
        return "State should be a valid state abbreviation (like FL) or blank";
    }    
    
    /**
     * Calculate the average rating given an array of review objects.
     *
     * @param array $reviews An array of review objects
     * @return integer
     */
    public function avgRating($reviews) {
        if (($reviewCount = count($reviews)) == 0) { return 0; }
        $total = 0;
        foreach ($reviews as $review) {
            $total += $review->my->rating;
        }
        return intval($total/$reviewCount);
    }

    /**
     * Update the review count and average rating of a Restaurant
     *
     * @param XN_Content $subject
     */
    public function updateStats($subject) {
        $reviews = $subject->my->content(Config::$reviewAttribute,true);
        $subject->my->reviewCount = count($reviews);
        $subject->my->avgRating = $this->avgRating($reviews);
        $subject->save();
    }
    
    /**
     * Figure out (for the purposes of addSubject.php) whether a particular
     * restaurant is already saved. If a restaurant content object with the title 
     * and description submitted in the form exists, it's returned. Otherwise
     * the function returns null, which tells addSubject.php to create a new one
     *
     * @return mixed
     */
    public function exists() {
        try {
            $subject = XN_Query::create('Content')
            ->filter('owner','=')
            ->filter('type','eic',Config::$subjectType)
            ->filter('title','eic',$_POST[Config::$subjectType.':title'])
            ->filter('description', 'eic',$_POST[Config::$subjectType.':description'])
            ->uniqueResult();
        } catch (Exception $e) {
            // If there's more than one matching result
            $subject = null;
        }
        return $subject;
    }

    
}

/**
 * This class describes the shape of the reviews. The name of the class
 * should be the same as the Config::$reviewType variable.
 *
 */
class RestaurantReview {
    /**
     * Numeric rating of the restaurant
     *
     * @var integer
     */
    public $rating;
    /**
     * What dishes did you eat at the restaurant?
     *
     * @var string
     */
    public $dishes;
    /**
     * Text review of the restaurant
     *
     * @var string
     */
    public $review;

    /**
     * Set up rules and types
     *
     */
    public function __construct() {
        $this->_types['rating'] = XN_Attribute::NUMBER;
        $this->_rules['rating'] = array('required');
        $this->_rules['dishes'] = array('required', array('maxlength', 100));
        $this->_rules['review'] = array('required', array('maxlength', 4000)); 
    }

    /**
     * Rating must be an integer between 1 and 5
     *
     * @param integer $value The submitted form value for rating
     * @return string
     */
    public function validate_rating($value) {
        if (! (ctype_digit($value) &&
        ($value >= 1) && ($value <=5))) {
            return "Rating should be a number between 1 and 5";
        }
    }
    
}

Config::load();
