# 
# view.rb
# vienna
# 
# Created by Adam Beynon.
# Copyright 2010 Adam Beynon.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
# 
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
# 
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#

require 'foundation/core/responder'
require 'foundation/core/builder'

module CherryKit
  
  class View < Responder
    
    # Get the layout hash from the receiver (Hash)
    attr_accessor :layout
    
    # the view's window
    attr_reader :window
    
    # Boolean whether or not the view can receive multi-touch events. Default
    # is false. If false (default), then the view is only sent details of the
    # first touch in the view, all other touches will be ignored within CKApp
    # and not passed to this view.
    # 
    # @getter multiple_touch_enabled?
    # 
    # @attribute {true|false} multiple_touch_enabled
    # 
    attr_writer :multiple_touch_enabled
    
    def initialize(frame)
      # initialize
      # default layout
      @layout = {
        :left   => 0,
        :top    => 0,
        :right  => 0,
        :bottom => 0
      }
      # all of our subviews
      @subviews = []
      
      # by default only receive single touches
      @multiple_touch_enabled = false
      
      # for every display property defined, we add an observer when it changes,
      # so we can tell the view that it needs a redisplay
      self.class.all_display_attributes.each do |property|
        self.observe(property) do |oldvalue, newvalue|
          self.needs_display = true
        end
      end
    end
    
    # For duplicating views. This will duplicate all relevant properties.
    # Subclasses should do their own behaviour. Does NOT copy subviews,
    # superview or window
    # 
    def dup
      result = self.class.new
      result.layout = layout
      
      result
    end
    
    # ======================
    # = Display attributes =
    # ======================
    
    def self.all_display_attributes
      if CherryKit::View == self
        return @display_attributes ||= []
      else
        @display_attributes ||= []
        return @display_attributes + superclass.all_display_attributes
      end
    end
    
    # Add each of the given display properties to the array of properties for
    # this class
    def self.display_attributes(*properties)
      @display_attributes ||= []
      @display_attributes = @display_attributes + properties
    end
    
    # Set each of the given class names (strings) to the array of class names
    # which will all be added together with superclass' to build up the full
    # class name. (e.g. CK::Control will be 'ck-view ck-control')
    # 
    def self.class_names(*names)
      # puts "setting class names to #{names.inspect} for #{self}"
      @css_class_names = names
    end
    
    def self.all_class_names
      # puts "looking for #{self} with #{@css_class_names.inspect}"
      if CherryKit::View == self
        return @css_class_names ||= []
      else
        @css_class_names ||= []
        return ([] + superclass.all_class_names) + @css_class_names
      end
    end
    
    display_attributes :visible, :layout
    
    class_names 'ck-view'
    
    # Return the theme name to use for the view. In all systems, root_theme is
    # used as the default. To use another theme, set the theme_name property for
    # the window when created so that all subviews will inherit that theme.
    # 
    # @returns {Symbol} theme name
    # 
    def theme_name
      :root_theme
    end
    
    # Returns the receivers container view
    # 
    # @returns [View] containing view
    # 
    def superview
      @superview
    end
    
    def render_context
      @render_context
    end
    
    # NEVER EVER call this method directly. This will create and / or update
    # the rendering context as needed
    # 
    def display
      if @render_context
        update
      else
        render_context = create_render_context
        # first call .render(), then immediately update() it
        render render_context
        update
        # add to super
        @superview.render_context.element << render_context.element
      end
    end
    
    # Create the render_context based on the theme and create_renderer()
    def create_render_context
      return @render_context if @render_context
      
      render_context = RenderContext.new tag_name            
      theme = Theme.find_theme theme_name
      # unless we could find the theme, throw an error - theme must exist
      theme or raise "Cannot find theme named #{theme_name}"
      # get our renderer. unless overridden, this will be theme::View renderer
      @renderer = create_renderer theme
      
      @render_context = render_context
    end
    
    # Create the renderer just for this view. The default action is to create
    # a simple view renderer. In this case, the most likely behaviour is that
    # .render() and .update() of this view should be overridden for rendering
    # custom views/data etc
    # 
    # @param {CherryKit::Theme} theme to create renderer from
    # @returns {CherryKit::Renderer} renderer
    # 
    def create_renderer(theme)
      theme.view self
    end
    
    # Core method for the initial render of the view. This method is passed the
    # render context that we render to. The default behaviour is to simply call
    # on the @renderer to render itself in the given context. Any view that does
    # not have a themed renderer should use this method instead and MUST call
    # super()
    # 
    # @param {CherryKit::RenderContext} render_context to render to
    # @returns nil
    # 
    def render(render_context)
      @renderer.render render_context
    end
    
    # Core method for updating the view. This is called immediately after render
    # and also everytime the view needs an update (self.needs_display=true).
    # Again the default behaviour is to simply call .update() on the @renderer,
    # but non themed views may simply have their own code here for updating, 
    # but as before, MUST call super() to allow the default ViewRenderer do its
    # business
    # 
    # @returns nil
    # 
    def update
      @renderer.update
    end
    
    
    
    def visible?
      true
    end
    
    # Bounds of the view. This often needs to be recalculated based on css
    # layout etc.
    # 
    # @returns {Browser::Rect} rect bounds
    def bounds
      Browser::Rect.new 0, 0, `#{render_context.element}.__element__.clientWidth`, `#{render_context.element}.__element__.clientHeight`
    end

    
    # Root element tag_name used for building the responder context. Should be a
    # Symbol. Default is <tt>:div</tt>
    # 
    # @returns {Symbol} tag name
    # 
    def tag_name
      :div
    end
    
    def <<(subview)
      add_subview subview
    end
    
    # Add subview
    # 
    # @param {CherryKit::View} view to append as subview
    # @returns {self}
    # 
    def add_subview(subview)
      # inform subview that it must first remove itself from its superview
      subview.remove_from_superview
      # privately set the window to our current window
      subview._window = @window
      # notify subview that it is soon to move to this view
      subview.will_move_to_superview self
      # set private superview variable on subview
      subview.instance_variable_set :@superview, self
      # do DOM manipulation here
      @subviews << subview
      # reset responder chain for subview
      subview.next_responder = self
      # alert subview that its move is complete
      subview.did_move_to_superview self
      # any callbacks that might be ndded
      did_add_subview subview
    end
    
    # Remove the receiver from its current superview
    # 
    def remove_from_superview
      
    end
    
    # Perform additonal actions once the subview has been added to the 
    # receiver
    # 
    # @param {CherryKit::View} subview that was added
    # @returns {nil}
    # 
    def did_add_subview(subview)
      # nothing by default
    end
    
    # Called when the receiver is about to move to the given superview
    # 
    # @param {CherryKit::View} view to move to
    # 
    def will_move_to_superview(superview)
      # nothing by default
    end
    
    # Called when the receiver has just moved to the given superview. Default 
    # action is to simply call self.needs_display which marks this view as 
    # needing display. This should always be called in a custom overridden
    # method, or just use super().
    # 
    # @param {CherryKit::View} view that is now the superview
    # 
    def did_move_to_superview(superview)
      self.needs_display = true
    end
    
    # Marks the receiver as needing displaying (rendering). Windows are in
    # charge of calling renderers etc as needed, so this method simply
    # registers itself with its window as needing display.
    # 
    # @param {true|false} needs_displaying
    # 
    def needs_display=(needs_displaying)
      # puts "======= needs_display"
      # we should only mark ourself as needing display if we have a window
      if @window
        RunLoop.current_run_loop.add_task self, :display
      end
    end
    
    # Sets the window for the view. This method should never be directly called.
    # Instead, use <tt><<</tt> to add the view to another view within the window
    # hierarchy.
    # 
    # @private
    # 
    # @param {CherryKit::Window} window to set
    # 
    def _window=(window)
      # puts "setting window to #{window} for #{self}"
      # if we already belong to the window, just return
      return if @window == window
      # callback
      will_move_to_window window
      
      @window = window
      
      # mark ourselves as needing redisplay (before our subviews are)
      self.needs_display = true
      
      # inform each subview that we are all moving
      @subviews.each do |subview|
        subview._window = window
      end
      
      # second callback
      did_move_to_window window
    end
    
    # Callback informing the receiver that it is about to join the new window
    # 
    # @param {CherryKit::Window} window to join
    # 
    def will_move_to_window(window)
      # do nothing by default
    end
    
    # Inform the receiver that it has joined the new window
    # 
    # @param {CherryKit::Window} window just joint
    # 
    def did_move_to_window(window)
      # do nothing by default
    end
    
    # ==========
    # = Events =
    # ==========
    
    # IF the view should capture all touches (instead of allowing subviews to),
    # then return true. Default is false. ScrollView, for example, returns true
    # 
    def capture_touches?
      false
    end
    
    # Can the view receive multiple touches: true or false
    def multiple_touch_enabled?
      @multiple_touch_enabled
    end
    
    # ===================
    # = Handling events =
    # ===================
    
    # Handle mouse down. Default implementation simply calls super(), which 
    # passes the event back up to CK::Responders implementation which passes the
    # event onto the next_responder
    # 
    # @param {Browser::Event}
    # 
    def mouse_down(event)
      super event
    end
    
    # ================
    # = Touch Events =
    # ================
    def touches_began(touches, event)
      puts "#{self}#touches_began"
    end
    
    def touches_ended(touches, event)
      puts "#{self}#touches_ended"
    end
    
    def touches_moved(touches, event)
      puts "#{self}#touches_moved"
    end
    # 
    # def touched_cancelled(touches, event)
    #   
    # end
    
    
    # ==================================
    # = Register views to DOM id names =
    # ==================================
    def self.[]=(id, view)
      (@view_ids ||= {})[id] = view
    end
    
    def self.[](id)
      @view_ids[id]
    end
  end
end