module CASClient
  module Frameworks
    module Merb
      module Filter
        attr_reader :client

        
        def cas_filter
          @client ||= CASClient::Client.new(CASClient::Frameworks::Merb::Config.config)
          
          service_ticket = read_ticket(self)
          
          cas_login_url = client.add_service_to_login_url(read_service_url(self))
          
          last_service_ticket = session[:cas_last_valid_ticket]
          if (service_ticket && 
            last_service_ticket && 
            last_service_ticket.ticket == service_ticket.ticket && 
            last_service_ticket.service == service_ticket.service)
            # warn() rather than info() because we really shouldn't be re-validating the same ticket. 
            # The only time when this is acceptable is if the user manually does a refresh and the ticket
            # happens to be in the URL.
            log.warn("Reusing previously validated ticket since the new ticket and service are the same.")
            service_ticket = last_service_ticket
          elsif last_service_ticket &&
              !config[:authenticate_on_every_request] && 
              session[client.username_session_key]
            # Re-use the previous ticket if the user already has a local CAS session (i.e. if they were already
            # previously authenticated for this service). This is to prevent redirection to the CAS server on every
            # request.
            # This behaviour can be disabled (so that every request is routed through the CAS server) by setting
            # the :authenticate_on_every_request config option to false. 
            log.debug "Existing local CAS session detected for #{session[client.username_session_key].inspect}. "+
              "Previous ticket #{last_service_ticket.ticket.inspect} will be re-used."
            service_ticket = last_service_ticket
          end
          
          if service_ticket
            client.validate_service_ticket(service_ticket) unless service_ticket.has_been_validated?
            validation_response = service_ticket.response
            
            if service_ticket.is_valid?
              log.info("Ticket #{service_ticket.inspect} for service #{service_ticket.service.inspect} " + 
                "belonging to user #{validation_response.user.inspect} is VALID.")
                
              session[client.username_session_key] = validation_response.user
              session[client.extra_attributes_session_key] = validation_response.extra_attributes
              
              # Store the ticket in the session to avoid re-validating the same service
              # ticket with the CAS server.
              session[:cas_last_valid_ticket] = service_ticket
              return true
            else  
              log.warn("Ticket #{service_ticket.ticket.inspect} failed validation -- " + 
                "#{validation_response.failure_code}: #{validation_response.failure_message}")
              redirect cas_login_url
              return false
            end
          else
            log.warn("No ticket -- redirecting to #{cas_login_url}")
            redirect cas_login_url
            return false
          end
        end
        
        private
        # Copied from Rails adapter
        def read_ticket(controller)
          ticket = controller.params[:ticket]
          
          return nil unless ticket
          
          log.debug("Request contains ticket #{ticket.inspect}.")
          
          if ticket =~ /^PT-/
            ProxyTicket.new(ticket, read_service_url(controller), controller.params[:renew])
          else
            ServiceTicket.new(ticket, read_service_url(controller), controller.params[:renew])
          end
        end
        
        # Also copied from Rails adapter
        def read_service_url(controller)
          if config[:service_url]
            log.debug("Using explicitly set service url: #{config[:service_url]}")
            return config[:service_url]
          end
          
          params = controller.params.dup
          params.delete(:ticket)
          service_url = request.protocol + request.host / controller.url(params.to_hash.symbolize_keys!)
          log.debug("Guessed service url: #{service_url.inspect}")
          return service_url
        end
        
        def log
          ::Merb.logger
        end
        
        def config
          ::Merb::Plugins.config[:"rubycas-client"]
        end

      end
    end
  end
end