# Allows Rails routes to be matched to CMS pages, allowing arbitrary code that can be executed before the page is rendered.
#
# The primary goal of this is to provide human readable (and cachable) URLs for content_blocks. For example,
# a single 'Article' page can have a portlet that knows how to look up and display an Article by id. By default, this
# would look like this:
#   GET /article?id=120
#
# Unless the Article page is marked a 'cache enabled = false' this will cause problems. Plus that URL is ugly.
# With PageRoutes, you can have multiple URLs all map to the article page, like so:
#   GET /article/2010/12/30/article-1
#   GET /article/2011/1/18/article-2
# In both these cases, these URLs can be matched to a Rails Route which is linked to a page:
# GET /article/:year/:month/:day/:slug -> Articles Page
#
# Saving a new PageRoute will reload the Rails routes.
#
class Cms::PageRoute < ActiveRecord::Base


  belongs_to :page, :class_name => 'Cms::Page'
  has_many :conditions, :class_name => 'Cms::PageRouteCondition'
  has_many :requirements, :class_name => 'Cms::PageRouteRequirement'

  extend Cms::DefaultAccessible
  
  validates_presence_of :pattern, :page_id
  validates_uniqueness_of :pattern

  after_save :reload_routes


  # Determines if its safe to call any persistent methods on PageRoutes. This can be false if either the database doesn't exist,
  # or the page_routes table doesn't yet exist.
  #
  # @return [Boolean] Whether its safe to call any ActiveRecord persistent method or not.
  def self.can_be_loaded?
    database_exists? && table_exists?
  end

  # Force Rails to reload the routes. Allows modules to call this without concern that the Rails classes are going to change again.
  def self.reload_routes
    Rails.application.reload_routes!
  end

  def reload_routes
    Cms::PageRoute.reload_routes
  end

  def add_condition(name, value)
    conditions.build(:name => name.to_s, :value => value.to_s)
  end

  # @deprecated Use add_constraint instead (matches Rails 3 syntax)
  def add_requirement(name, value)
    requirements.build(:name => name.to_s, :value => value.to_s)
  end

  alias_method :add_constraint, :add_requirement

  # @deprecated Rails 3 no longer uses a 'conditions' element in its syntax for routing.
  def conditions_map
    conditions.inject({}) { |acc, e| acc[e.name.to_sym] = e.value.to_sym; acc }
  end


  def requirements_map
    requirements.inject({}) { |acc, e| acc[e.name.to_sym] = Regexp.new(e.value); acc }
  end

  def route_name
    name ? name.to_slug.gsub('-', '_') : nil
  end

  alias_method :as, :route_name

  def to
    "cms/content#show_page_route"
  end

  # @param [Symbol | Array] method A method name (like :get) or array of names (ie. [:get :post]) to constraint this route.
  def via=(method)
    if method.respond_to?(:each)
      method.each do |m|
        add_condition(:method, m)
      end
    else
      add_condition(:method, method)
    end
  end

  # Returns which methods this route can be via. Defaults to [:get, :post] if not specified.
  def via
    found = conditions.collect() { |condition|
      if condition.name.to_sym == :method;
        condition.value.to_sym
      end }
    methods = found.compact
    if methods.empty?
      methods << :get << :post
    end
    methods
  end

  # Builds a hash which can be passed to the :constraints value in a route, like:
  #
  # match 'some/:pattern', :constraints => page_route.constraints()
  def constraints
    requirements_map
  end

  # This is used in defining the route in the ActionController::Routing
  # Used in Rails 2 version of routing (No longer valid for rails 3)
  # @deprecated
  def options_map
    m = {:controller => "cms/content", :action => "show_page_route"}

    m[:_page_route_id] = self.id.to_s

    m[:requirements] = requirements_map
    m[:conditions] = conditions_map

    m
  end

  def page_route_id
    self.id.to_s
  end

  # This is called by an instance of the content controller 
  # in the process of rendering a page.  This will eval the code
  # stored in this page route in the context of the controller.  
  # The main purpose of this method is to set instance variables 
  # that will be used by one or more portlets when the page is rendered.  
  # To set an instance variable, the code should contain something like:
  #    @news_article = NewsArticle.find(params[:id]))
  def execute(controller)
    controller.instance_eval(code) unless code.blank?
  end

end
