# == Schema Information
# Schema version: 108
#
# Table name: info_requests
#
#  id                        :integer         not null, primary key
#  title                     :text            not null
#  user_id                   :integer         not null
#  public_body_id            :integer         not null
#  created_at                :datetime        not null
#  updated_at                :datetime        not null
#  described_state           :string(255)     not null
#  awaiting_description      :boolean         default(FALSE), not null
#  prominence                :string(255)     default("normal"), not null
#  url_title                 :text            not null
#  law_used                  :string(255)     default("foi"), not null
#  allow_new_responses_from  :string(255)     default("anybody"), not null
#  handle_rejected_responses :string(255)     default("bounce"), not null
#  idhash                    :string(255)     not null
#


require 'digest/sha1'

class InfoRequest < ActiveRecord::Base
    strip_attributes!

    validates_presence_of :title, :message => N_("Please enter a summary of your request")
    validates_format_of :title, :with => /[a-zA-Z]/, :message => N_("Please write a summary with some text in it"), :if => Proc.new { |info_request| !info_request.title.nil? && !info_request.title.empty? }

    belongs_to :user
    #validates_presence_of :user_id # breaks during construction of new ones :(

    belongs_to :public_body
    validates_presence_of :public_body_id

    has_many :outgoing_messages, :order => 'created_at'
    has_many :incoming_messages, :order => 'created_at'
    has_many :info_request_events, :order => 'created_at'
    has_many :user_info_request_sent_alerts
    has_many :track_things, :order => 'created_at desc'
    has_many :comments, :order => 'created_at'
    has_many :censor_rules, :order => 'created_at desc'
    has_many :exim_logs, :order => 'exim_log_done_id'

    has_tag_string

    # user described state (also update in info_request_event, admin_request/edit.rhtml)
    validate :must_be_valid_state

    validates_inclusion_of :prominence, :in => [ 
        'normal', 
        'backpage',
        'hidden',
        'requester_only'
    ]

    validates_inclusion_of :law_used, :in => [ 
        'foi', # Freedom of Information Act
        'eir', # Environmental Information Regulations
    ]

    # who can send new responses
    validates_inclusion_of :allow_new_responses_from, :in => [
        'anybody', # anyone who knows the request email address
        'authority_only', # only people from authority domains
        'nobody'
    ]
    # what to do with refused new responses
    validates_inclusion_of :handle_rejected_responses, :in => [
        'bounce', # return them to sender
        'holding_pen', # put them in the holding pen
        'blackhole' # just dump them
    ]

    def self.enumerate_states
        states = [ 
        'waiting_response',
        'waiting_clarification', 
        'gone_postal',
        'not_held',
        'rejected', # this is called 'refused' in UK FOI law and the user interface, but 'rejected' internally for historic reasons
        'successful', 
        'partially_successful',
        'internal_review',
        'error_message',
        'requires_admin',
        'user_withdrawn'
        ]
        if @@custom_states_loaded
            states += InfoRequest.theme_extra_states
        end
        states
    end

    def must_be_valid_state
        errors.add(:described_state, "is not a valid state") if 
            !InfoRequest.enumerate_states.include? described_state
    end

    @@custom_states_loaded = false
    begin
        if ENV["RAILS_ENV"] != "test"
            require 'customstates'
            include InfoRequestCustomStates
            @@custom_states_loaded = true
        end
    rescue MissingSourceFile, NameError
    end

    # only check on create, so existing models with mixed case are allowed
    def validate_on_create
#        if !self.title.nil? && !MySociety::Validate.uses_mixed_capitals(self.title, 10)
#            errors.add(:title, _('Please write the summary using a mixture of capital and lower case letters. This makes it easier for others to read.'))
#        end
        if !self.title.nil? && title.size > 200
            errors.add(:title, _('Please keep the summary short, like in the subject of an email. You can use a phrase, rather than a full sentence.'))
        end
        if !self.title.nil? && self.title =~ /^(FOI|Freedom of Information)\s*requests?$/i
            errors.add(:title, _('Please describe more what the request is about in the subject. There is no need to say it is an FOI request, we add that on anyway.'))
        end
    end
    
    OLD_AGE_IN_DAYS = 21.days

    def after_initialize
        if self.described_state.nil?
            self.described_state = 'waiting_response'
        end
        # FOI or EIR?
        if !self.public_body.nil? && self.public_body.eir_only?
            self.law_used = 'eir'
        end
    end

    def visible_comments
        self.comments.find(:all, :conditions => 'visible')
    end

    # Central function to do all searches
    # (Not really the right place to put it, but everything can get it here, and it
    # does *mainly* find info requests, via their events, so hey)
    def InfoRequest.full_search(models, query, order, ascending, collapse, per_page, page)
        offset = (page - 1) * per_page

        return ::ActsAsXapian::Search.new(
            models, query,
            :offset => offset, :limit => per_page,
            :sort_by_prefix => order,
            :sort_by_ascending => ascending,
            :collapse_by_prefix => collapse
        )
    end

    # If the URL name has changed, then all request: queries will break unless
    # we update index for every event. Also reindex if prominence changes.
    after_update :reindex_some_request_events
    def reindex_some_request_events
        if self.changes.include?('url_title') || self.changes.include?('prominence') || self.changes.include?('user_id')
            self.reindex_request_events
        end
    end
    def reindex_request_events
        for info_request_event in self.info_request_events
            info_request_event.xapian_mark_needs_index
        end
    end
    # Force reindex when tag string changes
    alias_method :orig_tag_string=, :tag_string= 
    def tag_string=(tag_string)
        ret = self.orig_tag_string=(tag_string)
        reindex_request_events
        return ret
    end

    # Removes anything cached about the object in the database, and saves
    def clear_in_database_caches!
        for incoming_message in self.incoming_messages
            incoming_message.clear_in_database_caches!
        end
    end

    # For debugging
    def InfoRequest.profile_search(query)
        t = Time.now.usec
        for i in (1..10)
            t = Time.now.usec - t
            secs = t / 1000000.0
            STDOUT.write secs.to_s + " query " + i.to_s + "\n"
            results = InfoRequest.full_search([InfoRequestEvent], query, "created_at", true, nil, 25, 1).results
        end
    end

public
    # When name is changed, also change the url name
    def title=(title)
        write_attribute(:title, title)
        self.update_url_title
    end
    def update_url_title
        url_title = MySociety::Format.simplify_url_part(self.title, 'request', 32)
        # For request with same title as others, add on arbitary numeric identifier
        unique_url_title = url_title
        suffix_num = 2 # as there's already one without numeric suffix
        while not InfoRequest.find_by_url_title(unique_url_title, :conditions => self.id.nil? ? nil : ["id <> ?", self.id] ).nil?
            unique_url_title = url_title + "_" + suffix_num.to_s
            suffix_num = suffix_num + 1
        end
        write_attribute(:url_title, unique_url_title)
    end
    # Remove spaces from ends (for when used in emails etc.)
    # Needed for legacy reasons, even though we call strip_attributes now
    def title
        title = read_attribute(:title)
        if title
            title.strip!
        end
        return title
    end

    # Email which public body should use to respond to request. This is in
    # the format PREFIXrequest-ID-HASH@DOMAIN. Here ID is the id of the 
    # FOI request, and HASH is a signature for that id.
    def incoming_email
        return self.magic_email("request-")
    end
    def incoming_name_and_email
        return TMail::Address.address_from_name_and_email(self.user.name, self.incoming_email).to_s
    end

    # Subject lines for emails about the request
    def email_subject_request
        # XXX pull out this general_register_office specialisation
        # into some sort of separate jurisdiction dependent file
        if self.public_body.url_name == 'general_register_office'
            # without GQ in the subject, you just get an auto response
            _('{{law_used_full}} request GQ - {{title}}',:law_used_full=>self.law_used_full,:title=>self.title)
        else
            _('{{law_used_full}} request - {{title}}',:law_used_full=>self.law_used_full,:title=>self.title)
        end
    end
    def email_subject_followup(incoming_message = nil)
        if incoming_message.nil? || !incoming_message.valid_to_reply_to?
            'Re: ' + self.email_subject_request
        else
            if incoming_message.subject.match(/^Re:/i)
                incoming_message.subject
            else
                'Re: ' + incoming_message.subject
            end
        end
    end

    # Two sorts of laws for requests, FOI or EIR 
    def law_used_full
        if self.law_used == 'foi'
            return _("Freedom of Information")
        elsif self.law_used == 'eir'
            return _("Environmental Information Regulations")
        else
            raise "Unknown law used '" + self.law_used + "'"
        end
    end
    def law_used_short
        if self.law_used == 'foi'
            return _("FOI")
        elsif self.law_used == 'eir'
            return _("EIR")
        else
            raise "Unknown law used '" + self.law_used + "'"
        end
    end
    def law_used_act
        if self.law_used == 'foi'
            return _("Freedom of Information Act")
        elsif self.law_used == 'eir'
            return _("Environmental Information Regulations")
        else
            raise "Unknown law used '" + self.law_used + "'"
        end
    end
    def law_used_with_a
        if self.law_used == 'foi'
            return _("A Freedom of Information request")
        elsif self.law_used == 'eir'
            return _("An Environmental Information Regulations request")
        else
            raise "Unknown law used '" + self.law_used + "'"
        end
    end


    # Return info request corresponding to an incoming email address, or nil if
    # none found. Checks the hash to ensure the email came from the public body -
    # only they are sent the email address with the has in it. (We don't check
    # the prefix and domain, as sometimes those change, or might be elided by
    # copying an email, and that doesn't matter)
    def InfoRequest.find_by_incoming_email(incoming_email)
        id, hash = InfoRequest._extract_id_hash_from_email(incoming_email)
        return self.find_by_magic_email(id, hash)
    end

    # Return list of info requests which *might* be right given email address
    # e.g. For the id-hash email addresses, don't match the hash.
    def InfoRequest.guess_by_incoming_email(incoming_message)
        guesses = []
        # 1. Try to guess based on the email address(es)
        addresses =
            (incoming_message.mail.to || []) + 
            (incoming_message.mail.cc || []) +
            (incoming_message.mail.envelope_to || [])
        addresses.uniq!
        for address in addresses
            id, hash = InfoRequest._extract_id_hash_from_email(address)
            guesses.push(InfoRequest.find_by_id(id))
            guesses.push(InfoRequest.find_by_idhash(hash))
        end
        return guesses.select{|x| !x.nil?}.uniq
    end

    # Internal function used by find_by_magic_email and guess_by_incoming_email
    def InfoRequest._extract_id_hash_from_email(incoming_email)
        # Match case insensitively, FOI officers often write Request with capital R.
        incoming_email = incoming_email.downcase

        # The optional bounce- dates from when we used to have separate emails for the envelope from.
        # (that was abandoned because councils would send hand written responses to them, not just
        # bounce messages)
        incoming_email =~ /request-(?:bounce-)?([a-z0-9]+)-([a-z0-9]+)/
        id = $1.to_i
        hash = $2

        if not hash.nil?
            # Convert l to 1, and o to 0. FOI officers quite often retype the
            # email address and make this kind of error.
            hash.gsub!(/l/, "1")
            hash.gsub!(/o/, "0")
        end

        return [id, hash]
    end


    # When constructing a new request, use this to check user hasn't double submitted.
    # XXX could have a date range here, so say only check last month's worth of new requests. If somebody is making
    # repeated requests, say once a quarter for time information, then might need to do that.
    # XXX this *should* also check outgoing message joined to is an initial
    # request (rather than follow up)
    def InfoRequest.find_by_existing_request(title, public_body_id, body)
        return InfoRequest.find(:first, :conditions => [ "title = ? and public_body_id = ? and outgoing_messages.body = ?", title, public_body_id, body ], :include => [ :outgoing_messages ] )
    end

    def find_existing_outgoing_message(body)
        # XXX can add other databases here which have regexp_replace
        if ActiveRecord::Base.connection.adapter_name == "PostgreSQL"
            # Exclude spaces from the body comparison using regexp_replace
            return self.outgoing_messages.find(:first, :conditions => [ "regexp_replace(outgoing_messages.body, '[[:space:]]', '', 'g') = regexp_replace(?, '[[:space:]]', '', 'g')", body ])
        else
            # For other databases (e.g. SQLite) not the end of the world being space-sensitive for this check
            return self.outgoing_messages.find(:first, :conditions => [ "outgoing_messages.body = ?", body ])
        end
    end

    # Has this email already been received here? Based just on message id.
    def already_received?(email, raw_email_data)
        message_id = email.message_id
        if message_id.nil?
            raise "No message id for this message"
        end

        for im in self.incoming_messages
            other_message_id = im.mail.message_id
            if message_id == other_message_id
                return true
            end
        end

        return false
    end

    # A new incoming email to this request
    def receive(email, raw_email_data, override_stop_new_responses = false, rejected_reason = "")
        if !override_stop_new_responses
            allow = nil
            reason = nil
            # See if new responses are prevented for spam reasons
            if self.allow_new_responses_from == 'nobody'
                allow = false
                reason = _('This request has been set by an administrator to "allow new responses from nobody"')
            elsif self.allow_new_responses_from == 'anybody'
                allow = true
            elsif self.allow_new_responses_from == 'authority_only'
                if email.from_addrs.nil? || email.from_addrs.size == 0
                    allow = false
                    reason = _('Only the authority can reply to this request, but there is no "From" address to check against')
                else
                    sender_email = email.from_addrs[0].spec
                    sender_domain = PublicBody.extract_domain_from_email(sender_email)
                    reason = _("Only the authority can reply to this request, and I don't recognise the address this reply was sent from")
                    allow = false
                    # Allow any domain that has already sent reply
                    for row in self.who_can_followup_to
                        request_domain = PublicBody.extract_domain_from_email(row[1])
                        if request_domain == sender_domain
                            allow = true
                        end
                    end
                end
            else
                raise "Unknown allow_new_responses_from '" + self.allow_new_responses_from + "'"
            end

            if !allow
                if self.handle_rejected_responses == 'bounce'
                    RequestMailer.deliver_stopped_responses(self, email, raw_email_data)
                elsif self.handle_rejected_responses == 'holding_pen'
                    InfoRequest.holding_pen_request.receive(email, raw_email_data, false, reason)
                elsif self.handle_rejected_responses == 'blackhole'
                    # do nothing - just lose the message (Note: a copy will be
                    # in the backup mailbox if the server is configured to send
                    # new incoming messages there as well as this script)
                else
                    raise "Unknown handle_rejected_responses '" + self.handle_rejected_responses + "'"
                end
                return
            end
        end

        # Otherwise log the message
        incoming_message = IncomingMessage.new

        ActiveRecord::Base.transaction do
            raw_email = RawEmail.new
            incoming_message.raw_email = raw_email
            incoming_message.info_request = self
            incoming_message.save!
            raw_email.data = raw_email_data
            raw_email.save!

            self.awaiting_description = true
            params = { :incoming_message_id => incoming_message.id }
            if !rejected_reason.empty?
                params[:rejected_reason] = rejected_reason
            end
            self.log_event("response", params)
            self.save!
        end
        self.info_request_events.each { |event| event.xapian_mark_needs_index } # for the "waiting_classification" index
        RequestMailer.deliver_new_response(self, incoming_message)
    end


    # An annotation (comment) is made
    def add_comment(body, user)
        comment = Comment.new

        ActiveRecord::Base.transaction do
            comment.body = body
            comment.user = user
            comment.comment_type = 'request'
            comment.info_request = self
            comment.save!

            self.log_event("comment", { :comment_id => comment.id })
            self.save!
        end

        return comment
    end

    # The "holding pen" is a special request which stores incoming emails whose
    # destination request is unknown.
    def InfoRequest.holding_pen_request
        ir = InfoRequest.find_by_url_title("holding_pen")
        if ir.nil?
            ir = InfoRequest.new(
                :user => User.internal_admin_user,
                :public_body => PublicBody.internal_admin_body,
                :title => 'Holding pen',
                :described_state => 'waiting_response',
                :awaiting_description => false,
                :prominence  => 'backpage'
            )
            om = OutgoingMessage.new({
                :status => 'ready',
                :message_type => 'initial_request',
                :body => 'This is the holding pen request. It shows responses that were sent to invalid addresses, and need moving to the correct request by an adminstrator.',
                :last_sent_at => Time.now(),
                :what_doing => 'normal_sort'

            })
            ir.outgoing_messages << om
            om.info_request = ir
            ir.save!
            ir.log_event('sent', { :outgoing_message_id => om.id, :email => ir.public_body.request_email })
        end

        return ir
    end

    # states which require administrator action (hence email administrators
    # when they are entered, and offer state change dialog to them)
    def InfoRequest.requires_admin_states
        return ['requires_admin', 'error_message']
    end

    def requires_admin?
        return true if InfoRequest.requires_admin_states.include?(described_state)
        return false
    end

    # change status, including for last event for later historical purposes
    def set_described_state(new_state)
        ActiveRecord::Base.transaction do
            self.awaiting_description = false
            last_event = self.get_last_event
            last_event.described_state = new_state
            self.described_state = new_state
            last_event.save!
            self.save!
        end

        self.calculate_event_states

        if self.requires_admin?
            RequestMailer.deliver_requires_admin(self)
        end
    end

    # Work out what the situation of the request is. In addition to values of
    # self.described_state, can take these two values:
    #   waiting_classification
    #   waiting_response_overdue
    #   waiting_response_very_overdue
    def calculate_status
        if @@custom_states_loaded
            return self.theme_calculate_status
        else
            self.base_calculate_status
        end
    end
     
    def base_calculate_status
        return 'waiting_classification' if self.awaiting_description
        return described_state unless self.described_state == "waiting_response"
        # Compare by date, so only overdue on next day, not if 1 second late
        return 'waiting_response_very_overdue' if
            Time.now.strftime("%Y-%m-%d") > self.date_very_overdue_after.strftime("%Y-%m-%d")
        return 'waiting_response_overdue' if
            Time.now.strftime("%Y-%m-%d") > self.date_response_required_by.strftime("%Y-%m-%d")
        return 'waiting_response'
    end

    # Fill in any missing event states for first response before a description
    # was made. i.e. We take the last described state in between two responses
    # (inclusive of earlier), and set it as calculated value for the earlier
    # response.
    def calculate_event_states
        curr_state = nil
        for event in self.info_request_events.reverse
            event.xapian_mark_needs_index  # we need to reindex all events in order to update their latest_* terms
            if curr_state.nil? 
                if !event.described_state.nil?
                    curr_state = event.described_state
                end
            end

            if !curr_state.nil? && event.event_type == 'response' 
                if event.calculated_state != curr_state
                    event.calculated_state = curr_state
                    event.last_described_at = Time.now()
                    event.save!
                end
                if event.last_described_at.nil? # XXX actually maybe this isn't needed
                    event.last_described_at = Time.now()
                    event.save!
                end
                curr_state = nil
            elsif !curr_state.nil? && (event.event_type == 'followup_sent' || event.event_type == 'sent') && !event.described_state.nil? && (event.described_state == 'waiting_response' || event.described_state == 'internal_review')
                # Followups can set the status to waiting response / internal
                # review. Initial requests ('sent') set the status to waiting response.
               
                # We want to store that in calculated_state state so it gets
                # indexed.
                if event.calculated_state != event.described_state
                    event.calculated_state = event.described_state
                    event.last_described_at = Time.now()
                    event.save!
                end

                # And we don't want to propogate it to the response itself,
                # as that might already be set to waiting_clarification / a
                # success status, which we want to know about.
                curr_state = nil
            end
        end
    end

    # Find last outgoing message which  was:
    # -- sent at all
    # -- OR the same message was resent
    # -- OR the public body requested clarification, and a follow up was sent
    def last_event_forming_initial_request
        last_sent = nil
        expecting_clarification = false
        for event in self.info_request_events
            if event.described_state == 'waiting_clarification'
                expecting_clarification = true
            end

            if [ 'sent', 'resent', 'followup_sent', 'followup_resent' ].include?(event.event_type)
                if last_sent.nil?
                    last_sent = event
                elsif event.event_type == 'resent'
                    last_sent = event
                elsif expecting_clarification and event.event_type == 'followup_sent'
                    # XXX this needs to cope with followup_resent, which it doesn't.
                    # Not really easy to do, and only affects cases where followups
                    # were resent after a clarification.
                    last_sent = event
                    expecting_clarification = false
                end
            end
        end
        if last_sent.nil?
            raise "internal error, last_event_forming_initial_request gets nil for request " + self.id.to_s + " outgoing messages count " + self.outgoing_messages.size.to_s + " all events: " + self.info_request_events.to_yaml
        end
        return last_sent
    end

    # The last time that the initial request was sent/resent
    def date_initial_request_last_sent_at
        last_sent = last_event_forming_initial_request
        return last_sent.outgoing_message.last_sent_at
    end
    # How do we cope with case where extra info was required from the requester
    # by the public body in order to fulfill the request, as per sections 1(3)
    # and 10(6b) ? For clarifications this is covered by
    # last_event_forming_initial_request. There may be more obscure
    # things, e.g. fees, not properly covered.
    def date_response_required_by
        days_later = MySociety::Config.get('REPLY_LATE_AFTER_DAYS', 20)
        return Holiday.due_date_from(self.date_initial_request_last_sent_at, days_later)
    end
    # This is a long stop - even with UK public interest test extensions, 40
    # days is a very long time.
    def date_very_overdue_after
        last_sent = last_event_forming_initial_request
        very_late_days_later = MySociety::Config.get('REPLY_VERY_LATE_AFTER_DAYS', 40)
        school_very_late_days_later = MySociety::Config.get('SPECIAL_REPLY_VERY_LATE_AFTER_DAYS', 60)
        if self.public_body.is_school?
            # schools have 60 working days maximum (even over a long holiday)
            return Holiday.due_date_from(self.date_initial_request_last_sent_at, 60)
        else
            # public interest test ICO guidance gives 40 working maximum
            return Holiday.due_date_from(self.date_initial_request_last_sent_at, 40)
        end
    end

    # Where the initial request is sent to
    def recipient_email
        return self.public_body.request_email
    end
    def recipient_email_valid_for_followup?
        return self.public_body.is_followupable?
    end
    def recipient_name_and_email
        return TMail::Address.address_from_name_and_email(self.law_used_short + " requests at " + self.public_body.short_or_long_name, self.recipient_email).to_s
    end

    # History of some things that have happened
    def log_event(type, params)
        self.info_request_events.create!(:event_type => type, :params => params)
    end

    # The last response is the default one people might want to reply to
    def get_last_response_event_id
        for e in self.info_request_events.reverse
            if e.event_type == 'response'
                return e.id
            end
        end
        return nil

    end
    def get_last_response_event
        for e in self.info_request_events.reverse
            if e.event_type == 'response'
                return e
            end
        end
        return nil
    end
    def get_last_response
        last_response_event = self.get_last_response_event
        if last_response_event.nil?
            return nil
        else
            return last_response_event.incoming_message
        end
    end

    # The last outgoing message
    def get_last_outgoing_event
        for e in self.info_request_events.reverse
            if [ 'sent', 'followup_sent' ].include?(e.event_type)
                return e
            end
        end
        return nil
    end

    # Text from the the initial request, for use in summary display
    def initial_request_text
        if outgoing_messages.empty? # mainly for use with incomplete fixtures
            return ""
        end
        excerpt = self.outgoing_messages[0].get_text_for_indexing
        return excerpt
    end

    # Returns index of last event which is described or nil if none described.
    def index_of_last_described_event
        events = self.info_request_events
        events.each_index do |i|
            revi = events.size - 1 - i 
            m = events[revi] 
            if not m.described_state.nil?
                return revi
            end
        end
        return nil
    end

    def last_event_id_needing_description
        last_event = events_needing_description[-1]
        last_event.nil? ? 0 : last_event.id
    end        

    # Returns all the events which the user hasn't described yet - an empty array if all described.
    def events_needing_description
        events = self.info_request_events
        i = self.index_of_last_described_event
        if i.nil?
            return events
        else
            return events[i + 1, events.size]
        end
    end

    # Returns last event
    def get_last_event
        events = self.info_request_events
        if events.size == 0
            return nil
        else
            return events[-1]
        end
    end

    # Get previous email sent to
    def get_previous_email_sent_to(info_request_event)
        last_email = nil
        for e in self.info_request_events
            if ((info_request_event.is_sent_sort? && e.is_sent_sort?) || (info_request_event.is_followup_sort? && e.is_followup_sort?)) && e.outgoing_message_id == info_request_event.outgoing_message_id
                if e.id == info_request_event.id
                    break
                end
                last_email = e.params[:email]
            end
        end
        return last_email
    end


    # Display version of status
    def InfoRequest.get_status_description(status)
        if status == 'waiting_classification'
            _("Awaiting classification.")
        elsif status == 'waiting_response'
            _("Awaiting response.")
        elsif status == 'waiting_response_overdue'
            _("Delayed.")
        elsif status == 'waiting_response_very_overdue'
            _("Long overdue.")
        elsif status == 'not_held'
            _("Information not held.")
        elsif status == 'rejected'
            _("Refused.")
        elsif status == 'partially_successful'
            _("Partially successful.")
        elsif status == 'successful'
            _("Successful.")
        elsif status == 'waiting_clarification'
            _("Waiting clarification.")
        elsif status == 'gone_postal'
            _("Handled by post.")
        elsif status == 'internal_review'
            _("Awaiting internal review.")
        elsif status == 'error_message'
            _("Delivery error")
        elsif status == 'requires_admin'
            _("Unusual response.")
        elsif status == 'user_withdrawn'
            _("Withdrawn by the requester.")
        else
            begin
                return self.theme_display_status(status)
            rescue NoMethodError
                raise _("unknown status ") + status
            end
        end
    end

    def display_status
        InfoRequest.get_status_description(self.calculate_status)
    end

    # Completely delete this request and all objects depending on it
    def fully_destroy
        self.track_things.each do |track_thing|
            track_thing.track_things_sent_emails.each { |a| a.destroy }
            track_thing.destroy
        end
        self.user_info_request_sent_alerts.each { |a| a.destroy }
        self.info_request_events.each do |info_request_event| 
            info_request_event.track_things_sent_emails.each { |a| a.destroy }
            info_request_event.destroy
        end
        self.exim_logs.each do |exim_log| 
            exim_log.destroy
        end
        self.outgoing_messages.each { |a| a.destroy }
        self.incoming_messages.each { |a| a.destroy }
        self.destroy
    end

    # Called by incoming_email - and used to be called to generate separate
    # envelope from address until we abandoned it.
    def magic_email(prefix_part)
        raise "id required to make magic" if not self.id
        return InfoRequest.magic_email_for_id(prefix_part, self.id)
    end

    def InfoRequest.magic_email_for_id(prefix_part, id) 
        magic_email = MySociety::Config.get("INCOMING_EMAIL_PREFIX", "") 
        magic_email += prefix_part + id.to_s
        magic_email += "-" + InfoRequest.hash_from_id(id)
        magic_email += "@" + MySociety::Config.get("INCOMING_EMAIL_DOMAIN", "localhost")
        return magic_email
    end

    before_validation :compute_idhash

    def compute_idhash
        self.idhash = InfoRequest.hash_from_id(self.id)
    end

    def InfoRequest.hash_from_id(id)
        return Digest::SHA1.hexdigest(id.to_s + MySociety::Config.get("INCOMING_EMAIL_SECRET", 'dummysecret'))[0,8]
    end

    # Called by find_by_incoming_email - and used to be called by separate
    # function for envelope from address, until we abandoned it.
    def InfoRequest.find_by_magic_email(id, hash)
        expected_hash = InfoRequest.hash_from_id(id)
        #print "expected: " + expected_hash + "\nhash: " + hash + "\n"
        if hash != expected_hash
            return nil
        else
            begin
                return self.find(id)
            rescue ActiveRecord::RecordNotFound
                # so error email is sent to admin, rather than the exception sending weird
                # error to the public body.
                return nil
            end
        end
    end

    # Used to find when event last changed
    def InfoRequest.last_event_time_clause(event_type=nil)
        event_type_clause = ''
        event_type_clause = " and info_request_events.event_type = '#{event_type}'" if event_type
        "(select created_at from info_request_events where info_request_events.info_request_id = info_requests.id#{event_type_clause} order by created_at desc limit 1)"
    end

    def InfoRequest.find_old_unclassified(extra_params={})
        last_response_created_at = last_event_time_clause('response')
        age = extra_params[:age_in_days] ? extra_params[:age_in_days].days : OLD_AGE_IN_DAYS
        params = {:select => "*, #{last_response_created_at} as last_response_time", 
                  :conditions => ["awaiting_description = ? and #{last_response_created_at} < ? and url_title != 'holding_pen'", 
                                 true, Time.now() - age], 
                                 :order => "last_response_time"}
        params[:limit] = extra_params[:limit] if extra_params[:limit]
        params[:include] = extra_params[:include] if extra_params[:include]
        if extra_params[:order]
            params[:order] = extra_params[:order] 
            params.delete(:select)
        end
        if extra_params[:conditions]
            condition_string = extra_params[:conditions].shift
            params[:conditions][0] += " and #{condition_string}"
            params[:conditions] += extra_params[:conditions]
        end
        find(:all, params)
    end
    
    def is_old_unclassified?
        return false if !awaiting_description
        return false if url_title == 'holding_pen'
        last_response_event = get_last_response_event
        return false unless last_response_event
        return false if last_response_event.created_at >= Time.now - OLD_AGE_IN_DAYS
        return true
    end

    # List of incoming messages to followup, by unique email
    def who_can_followup_to(skip_message = nil)
        ret = []
        done = {}
        for incoming_message in self.incoming_messages.reverse
            if incoming_message == skip_message
                next
            end
            incoming_message.safe_mail_from
            
            email = OutgoingMailer.email_for_followup(self, incoming_message)
            name = OutgoingMailer.name_for_followup(self, incoming_message)

            if !done.include?(email.downcase)
                ret = ret + [[name, email, incoming_message.id]]
            end
            done[email.downcase] = 1
        end

        if !done.include?(self.public_body.request_email.downcase)
            ret = ret + [[self.public_body.name, self.public_body.request_email, nil]]
        end
        done[self.public_body.request_email.downcase] = 1

        return ret.reverse
    end

    # Call groups of censor rules
    def apply_censor_rules_to_text!(text)
        for censor_rule in self.censor_rules
            censor_rule.apply_to_text!(text)
        end
        if self.user # requests during construction have no user
            for censor_rule in self.user.censor_rules
                censor_rule.apply_to_text!(text)
            end
        end
    end
    
    def apply_censor_rules_to_binary!(binary)
        for censor_rule in self.censor_rules
            censor_rule.apply_to_binary!(binary)
        end
        if self.user # requests during construction have no user
            for censor_rule in self.user.censor_rules
                censor_rule.apply_to_binary!(binary)
            end
        end
    end
    
    def is_owning_user?(user)
        !user.nil? && (user.id == user_id || user.owns_every_request?)
    end
    def is_actual_owning_user?(user)
        !user.nil? && user.id == user_id
    end

    def user_can_view?(user)
        if self.prominence == 'hidden' 
            return User.view_hidden_requests?(user)
        end
        if self.prominence == 'requester_only' 
            return self.is_owning_user?(user)
        end
        return true
    end

    def indexed_by_search?
        if self.prominence == 'backpage' || self.prominence == 'hidden' || self.prominence == 'requester_only'
            return false
        end
        return true
    end

    # This is called from cron regularly.
    def InfoRequest.stop_new_responses_on_old_requests
        # 6 months since last change to request, only allow new incoming messages from authority domains
        InfoRequest.update_all "allow_new_responses_from = 'authority_only' where updated_at < (now() - interval '6 months') and allow_new_responses_from = 'anybody' and url_title <> 'holding_pen'"
        # 1 year since last change requests, don't allow any new incoming messages
        InfoRequest.update_all "allow_new_responses_from = 'nobody' where updated_at < (now() - interval '1 year') and allow_new_responses_from in ('anybody', 'authority_only') and url_title <> 'holding_pen'"
    end

    # Returns a random FOI request
    def InfoRequest.random
        max_id = InfoRequest.connection.select_value('select max(id) as a from info_requests').to_i
        info_request = nil
        count = 0
        while info_request.nil?
            if count > 100
                return nil
            end
            id = rand(max_id) + 1
            begin
                count += 1
                info_request = find(id, :conditions => ["prominence = 'normal'"])
            rescue ActiveRecord::RecordNotFound
            end
        end
        return info_request
    end

    def json_for_api(deep)
        ret = { 
            :id => self.id,
            :url_title => self.url_title,
            :title => self.title,
            :created_at => self.created_at,
            :updated_at => self.updated_at,
            :described_state => self.described_state,
            :display_status => self.display_status,
            :awaiting_description => self.awaiting_description ,
            :prominence => self.prominence,
            :law_used => self.law_used,
            :tags => self.tag_array,

            # not sure we need to make these, mainly anti-spam, admin params public
            # allow_new_responses_from
            # handle_rejected_responses
        }

        if deep
            ret[:user] = self.user.json_for_api
            ret[:public_body] = self.public_body.json_for_api
            ret[:info_request_events] = self.info_request_events.map { |e| e.json_for_api(false) }
        end
        return ret
    end
end


