#!/usr/bin/ruby -w

require 'strscan'
require 'date'

# io = DATA
io = ARGF

default_header = ENV['MM_SEARCH_SCOPE']
default_header = "#common" if default_header.nil?

input = io.gets.strip

# puts input

def is_leap_year (year)
   return (year & 3) == 0 && ((year % 25) != 0 || (year & 15) == 0)
end

$first_value = ""
def convert (tokens, start_index, header, filter)
  return if start_index >= tokens.size

  count = 0
  conjunction = "and"
  i = start_index
  loop do
    token = tokens[i]
    if token == "("
      filter << " " + conjunction + " " if count > 0
      filter << "("
      i = convert(tokens, i+1, header, filter)
      filter << ")"
      count += 1
      conjunction = "and"
    elsif token == ")"
      return i
    elsif token[-1, 1] == ":"
      header = token[0..-2]
    elsif token == "f" # ToDo: Make it easier to specify multiple headers, e.g., "f s foo bar" (searches from/subject for 'foo' and 'bar').
      header = "from"
    elsif token == "s"
      header = "subject"
    elsif token == "t"
      header = "#recipient"
    elsif token == "a"
      header = "#any-address"
    # elsif token == "c" # This is slow for large accounts. Needs to be optimized (or dropped for use here).
    #   header = "#any-address.#correspondent"
    elsif token == "b"
      header = "#unquoted"
    elsif token == "q"
      header = "#quoted"
    elsif token == "m"
      header = "#common"
    elsif token == "M"
      header = "#commonplus"
    elsif token == "A"
      header = "#filename"
    elsif token == "T"
      header = "##tags.tag.#name"
    elsif token == "K"
      header = "#flags.flag"
    elsif token == "d"
      header = "#date-received"
    elsif count > 0 and (token == "and" or token == "or")
      conjunction = token
    else
      negated = false
      if token[0] == '!'
        negated = true
        token = token[1..-1]
      end

      filter << " " + conjunction + " " if count > 0
      filter << header + " "

      if header == "#date-received"
         # Reformat date to support various formats
         # Allow "/.-" as separators in the date format
         token.gsub!('/', '-')
         token.gsub!('.', '-')
         if match = /^([<>])?\d{4}/.match(token)
            # This is expected to be the default format: YYYY[[-MM]-DD]
         else
            # Check for alternative format: DD[-MM[-YYYY]]
            if match = /^([<>])?(\d{1,2})(?:-(\d{1,2})(?:-(\d{4}))?)?$/.match(token) # Matches YYYY[[-MM]-DD]
               comp, day, month, year = match.captures
               if month == nil
                  current_month = Date.today.month
                  current_month = (current_month == 12 ? 1 : current_month-1) if day.to_i > Date.today.day
                  month = current_month.to_s
               end
               if year == nil
                  current_year = Date.today.year
                  current_year = current_year-1 if month.to_i > Date.today.month
                  year = current_year.to_s
               end
               month = month.rjust(2, '0')
               day = day.rjust(2, '0')

               token = "#{comp}#{year}-#{month}-#{day}"
            end
         end

         if match = /^([<>])?(\d{4})(?:-(\d{1,2})(?:-(\d{1,2}))?)?$/.match(token) # Matches YYYY[[-MM]-DD]
            comp, year, month, day = match.captures
            # puts match.captures

            date = year
            next_date = year
            if month == nil
               date = "'#{year}-01-01 00:00:00'"
               next_date = "'#{year.to_i + 1}-01-01 00:00:00'"
            elsif day == nil
               date = "'#{year}-#{month}-01 00:00:00'"
               if month == "12"
                  year = (year.to_i+1).to_s
                  month = "1"
               else
                  month = (month.to_i+1).to_s
               end
               month = month.rjust(2, '0')
               next_date = "'#{year}-#{month}-01 00:00:00'"
            else
               date = "'#{year}-#{month}-#{day} 00:00:00'"
               days_per_month = [ 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
               if day.to_i == days_per_month[month.to_i] or (month == "02" and day == "29" and is_leap_year(year.to_i))
                  month = (month.to_i+1).to_s
                  if month == "13"
                     year = (year.to_i+1).to_s
                     month = "1"
                  end
                  day = "1"
               else
                  day = (day.to_i+1).to_s
               end

               month = month.rjust(2, '0')
               day = day.rjust(2, '0')
               next_date = "'#{year}-#{month}-#{day} 00:00:00'"
            end

            if comp == '<' or comp == '>'
               filter << comp << " " << date
            else
               filter << "> " << date << " and " << header << " < " << next_date
            end
         elsif match = /^(\d+)([hdwmy])[a-z]*$/.match(token)
            number, factor = match.captures
            shortcuts = { "h" => "hours", "d" => "days", "w" => "weeks", "m" => "months", "y" => "years" }
            filter << (negated ? "<" : ">") << "[f] " << number << " " << shortcuts[factor] << " ago"
         elsif token == "today"
            filter << ">[f] 1 days ago"
         elsif token == "yesterday"
            filter << ">[f] 2 days ago and " << header << " <[f] 1 days ago"
         # Todo: Also support month names as a shortcut?
         end
      else
         filter << "!" if negated
         if header == "##tags.tag.#name" or header == "#flags.flag"
           filter << "="
         else
           filter << "~"
         end
         include_subparts = ["#common", "#commonplus", "#unquoted", "#quoted", "#filename"].include? header
         if include_subparts
           filter << "[a"
           filter << "x" if negated
           filter << "]"
         else
           filter << "[x]" if negated
         end
         filter << " " + "'" + token.gsub(/'/,'\'\'').gsub(/"/,'\\"') + "'"
      end

      count += 1
      conjunction = "and"
      $first_value = token if $first_value.empty? and include_subparts
    end

    i += 1
    break if tokens.size == i
  end
  return tokens.size
end

def tokenize (ss, tokens)
  loop do
    if ss.scan(/\(/) # Sub-conditions in parenthesis
      tokens << "("
      tokenize(ss, tokens)
      tokens << ")"
    elsif ss.scan(/\)/)
      break
    elsif word = ss.scan(/'([^']|'')*'/) # Single-quoted word. An inline quote is escaped using two single quotes
      word = word[1..-2]
      tokens << word.gsub(/''/,'\'')
    elsif word = ss.scan(/"([^"])*"/) # Double-quoted word. Doesn't allow inline (double)quotes
      tokens << word[1..-2]
    elsif word = ss.scan(/[^\s()\:]+:?/)
      tokens << word
    elsif ss.scan(/\s+/) # Skip whitespace
    else
      # Giving up...
      break
    end
  end
end

ss = StringScanner.new(input)
tokens = []
tokenize(ss, tokens)
filter = ""
convert(tokens, 0, default_header, filter)

print "{ actions = ( {"
print "type = search; "
print "filter = \"#{filter}\"; "
# print "title = \"#{title}\"; "
puts "} ); }"

`printf '#{$first_value}' | pbcopy -pboard find` if not $first_value.empty?

__END__
