require 'fileutils'
require 'redcloth_for_tex'
require 'parsedate'
require 'zip/zip'
require 'resolv'

class WikiController < ApplicationController

  before_filter :load_page
  caches_action :show, :published, :authors, :recently_revised, :list
  cache_sweeper :revision_sweeper

  layout 'default', :except => [:rss_feed, :rss_with_content, :rss_with_headlines, :tex,  :export_tex, :export_html]

  def index
    if @web_name
      redirect_home
    elsif not @wiki.setup?
      redirect_to :controller => 'admin', :action => 'create_system'
    elsif @wiki.webs.length == 1
      redirect_home @wiki.webs.values.first.address
    else
      redirect_to :action => 'web_list'
    end
  end

  # Outside a single web --------------------------------------------------------

  def authenticate
    if password_check(params['password'])
      redirect_home
    else 
      flash[:info] = password_error(params['password'])
      redirect_to :action => 'login', :web => @web_name
    end
  end

  def login
    # to template
  end
  
  def web_list
    @webs = wiki.webs.values.sort_by { |web| web.name }
  end


  # Within a single web ---------------------------------------------------------

  def authors
    @page_names_by_author = @web.page_names_by_author
    @authors = @page_names_by_author.keys.sort
  end
  
  def export_html
    stylesheet = File.read(File.join(RAILS_ROOT, 'public', 'stylesheets', 'instiki.css'))
    export_pages_as_zip('html') do |page| 

      renderer = PageRenderer.new(page.revisions.last)
      rendered_page = <<-EOL
        <!DOCTYPE html
        PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
        <html xmlns="http://www.w3.org/1999/xhtml">
        <head>
          <title>#{page.plain_name} in #{@web.name}</title>
          <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
  
          <style type="text/css">
            h1#pageName, .newWikiWord a, a.existingWikiWord, .newWikiWord a:hover { 
              color: ##{@web ? @web.color : "393" }; 
            }
            .newWikiWord { background-color: white; font-style: italic; }
            #{stylesheet}
          </style>
          <style type="text/css">
            #{@web.additional_style}
          </style>
        </head>
        <body>
          #{renderer.display_content_for_export}
          <div class="byline">
            #{page.revisions? ? "Revised" : "Created" } on #{ page.revised_at.strftime('%B %d, %Y %H:%M:%S') }
            by
            #{ UrlGenerator.new(self).make_link(page.author.name, @web, nil, { :mode => :export }) }
          </div>
        </body>
        </html>
      EOL
      rendered_page
    end
  end

  def export_markup
    export_pages_as_zip(@web.markup) { |page| page.content }
  end

  def export_pdf
    file_name = "#{@web.address}-tex-#{@web.revised_at.strftime('%Y-%m-%d-%H-%M-%S')}"
    file_path = File.join(@wiki.storage_path, file_name)

    export_web_to_tex "#{file_path}.tex"  unless FileTest.exists? "#{file_path}.tex"
    convert_tex_to_pdf "#{file_path}.tex"
    send_file "#{file_path}.pdf"
  end

  def export_tex
    file_name = "#{@web.address}-tex-#{@web.revised_at.strftime('%Y-%m-%d-%H-%M-%S')}.tex"
    file_path = File.join(@wiki.storage_path, file_name)
    export_web_to_tex(file_path) unless FileTest.exists?(file_path)
    send_file file_path
  end

  def feeds
    @rss_with_content_allowed = rss_with_content_allowed?
    # show the template
  end

  def list
    parse_category
    @page_names_that_are_wanted = @pages_in_category.wanted_pages
    @pages_that_are_orphaned = @pages_in_category.orphaned_pages
  end
  
  def recently_revised
    parse_category
    @pages_by_revision = @pages_in_category.by_revision
    @pages_by_day = Hash.new { |h, day| h[day] = [] }
    @pages_by_revision.each do |page| 
      day = Date.new(page.revised_at.year, page.revised_at.month, page.revised_at.day)
      @pages_by_day[day] << page
    end
  end

  def rss_with_content
    if rss_with_content_allowed?
      render_rss(hide_description = false, *parse_rss_params)
    else
      render_text 'RSS feed with content for this web is blocked for security reasons. ' +
        'The web is password-protected and not published', '403 Forbidden'
    end
  end

  def rss_with_headlines
    render_rss(hide_description = true, *parse_rss_params)
  end

  def search
    @query = params['query']
    @title_results = @web.select { |page| page.name =~ /#{@query}/i }.sort
    @results = @web.select { |page| page.content =~ /#{@query}/i }.sort
    all_pages_found = (@results + @title_results).uniq
    if all_pages_found.size == 1
      redirect_to_page(all_pages_found.first.name)
    end
  end

  # Within a single page --------------------------------------------------------
  
  def cancel_edit
    @page.unlock
    redirect_to_page(@page_name)
  end

  def edit
    if @page.nil?
      redirect_home
    elsif @page.locked?(Time.now) and not params['break_lock']
      redirect_to :web => @web_name, :action => 'locked', :id => @page_name
    else
      @page.lock(Time.now, @author)
    end
  end
  
  def locked
    # to template
  end
  
  def new
    # to template
  end

  def pdf
    page = wiki.read_page(@web_name, @page_name)
    safe_page_name = @page.name.gsub(/\W/, '')
    file_name = "#{safe_page_name}-#{@web.address}-#{@page.revised_at.strftime('%Y-%m-%d-%H-%M-%S')}"
    file_path = File.join(@wiki.storage_path, file_name)

    export_page_to_tex("#{file_path}.tex") unless FileTest.exists?("#{file_path}.tex")
    # NB: this is _very_ slow
    convert_tex_to_pdf("#{file_path}.tex")
    send_file "#{file_path}.pdf"
  end

  def print
    if @page.nil?
      redirect_home
    end
    @link_mode ||= :show
    @renderer = PageRenderer.new(@page.revisions.last)
    # to template
  end

  def published
    if not @web.published?
      render(:text => "Published version of web '#{@web_name}' is not available", :status => 404)
      return 
    end

    @page_name ||= 'HomePage'
    @page ||= wiki.read_page(@web_name, @page_name)
    render(:text => "Page '#{@page_name}' not found", :status => 404) and return unless @page
    
    @renderer = PageRenderer.new(@page.revisions.last)
  end
  
  def revision
    get_page_and_revision
    @show_diff = (params[:mode] == 'diff')
    @renderer = PageRenderer.new(@revision)
  end

  def rollback
    get_page_and_revision
  end

  def save
    render(:status => 404, :text => 'Undefined page name') and return if @page_name.nil?
    unless (request.post? || ENV["RAILS_ENV"] == "test")
      headers['Allow'] = 'POST'
      render(:status => 405, :text => 'You must use an HTTP POST')
      return
    end

    author_name = params['author']
    author_name = 'AnonymousCoward' if author_name =~ /^\s*$/
    cookies['author'] = { :value => author_name, :expires => Time.utc(2030) }
    
    begin
      filter_spam(params['content'])
      if @page
        wiki.revise_page(@web_name, @page_name, params['content'], Time.now, 
            Author.new(author_name, remote_ip), PageRenderer.new)
        @page.unlock
      else
        wiki.write_page(@web_name, @page_name, params['content'], Time.now, 
            Author.new(author_name, remote_ip), PageRenderer.new)
      end
      redirect_to_page @page_name
    rescue => e
      flash[:error] = e
      logger.error e
      flash[:content] = params['content']
      if @page
        @page.unlock
        redirect_to :action => 'edit', :web => @web_name, :id => @page_name
      else
        redirect_to :action => 'new', :web => @web_name, :id => @page_name
      end
    end
  end

  def show
    if @page
      begin
        @renderer = PageRenderer.new(@page.revisions.last)
        @show_diff = (params[:mode] == 'diff')
        render_action 'page'
      # TODO this rescue should differentiate between errors due to rendering and errors in 
      # the application itself (for application errors, it's better not to rescue the error at all)
      rescue => e
        logger.error e
        flash[:error] = e.message
        if in_a_web?
          redirect_to :action => 'edit', :web => @web_name, :id => @page_name
        else
          raise e
        end
      end
    else
      if not @page_name.nil? and not @page_name.empty?
        redirect_to :web => @web_name, :action => 'new', :id => @page_name
      else
        render_text 'Page name is not specified', '404 Not Found'
      end
    end
  end

  def tex
    @tex_content = RedClothForTex.new(@page.content).to_tex
  end

  protected
  
  def load_page
    @page_name = params['id']
    @page = @wiki.read_page(@web_name, @page_name) if @page_name
  end

  private

  def convert_tex_to_pdf(tex_path)
    # TODO remove earlier PDF files with the same prefix
    # TODO handle gracefully situation where pdflatex is not available
    begin
      wd = Dir.getwd
      Dir.chdir(File.dirname(tex_path))
      logger.info `pdflatex --interaction=nonstopmode #{File.basename(tex_path)}`
    ensure
      Dir.chdir(wd)
    end
  end

  def export_page_to_tex(file_path)
    tex
    File.open(file_path, 'w') { |f| f.write(render_to_string(:template => 'wiki/tex', :layout => false)) }
  end

  def export_pages_as_zip(file_type, &block)

    file_prefix = "#{@web.address}-#{file_type}-"
    timestamp = @web.revised_at.strftime('%Y-%m-%d-%H-%M-%S')
    file_path = File.join(@wiki.storage_path, file_prefix + timestamp + '.zip')
    tmp_path = "#{file_path}.tmp"

    Zip::ZipOutputStream.open(tmp_path) do |zip_out|
      @web.select.by_name.each do |page|
        zip_out.put_next_entry("#{CGI.escape(page.name)}.#{file_type}")
        zip_out.puts(block.call(page))
      end
      # add an index file, if exporting to HTML
      if file_type.to_s.downcase == 'html'
        zip_out.put_next_entry 'index.html'
        zip_out.puts "<html><head>" +
            "<META HTTP-EQUIV=\"Refresh\" CONTENT=\"0;URL=HomePage.#{file_type}\"></head></html>"
      end
    end
    FileUtils.rm_rf(Dir[File.join(@wiki.storage_path, file_prefix + '*.zip')])
    FileUtils.mv(tmp_path, file_path)
    send_file file_path
  end

  def export_web_to_tex(file_path)
    @tex_content = table_of_contents(@web.page('HomePage').content, render_tex_web)
    File.open(file_path, 'w') { |f| f.write(render_to_string(:template => 'wiki/tex_web', :layout => nil)) }
  end

  def get_page_and_revision
    if params['rev']
      @revision_number = params['rev'].to_i
    else
      @revision_number = @page.revisions.length
    end
    @revision = @page.revisions[@revision_number - 1]
  end

  def parse_category
    @categories = WikiReference.list_categories(@web).sort
    @category = params['category']
    if @category
      @set_name = "category '#{@category}'"
      pages = WikiReference.pages_in_category(@web, @category).sort.map { |page_name| @web.page(page_name) }
      @pages_in_category = PageSet.new(@web, pages)
    else
      # no category specified, return all pages of the web
      @pages_in_category = @web.select_all.by_name
      @set_name = 'the web'
    end
  end

  def parse_rss_params
    if params.include? 'limit'
      limit = params['limit'].to_i rescue nil
      limit = nil if limit == 0
    else
      limit = 15
    end
    start_date = Time.local(*ParseDate::parsedate(params['start'])) rescue nil
    end_date = Time.local(*ParseDate::parsedate(params['end'])) rescue nil
    [ limit, start_date, end_date ]
  end
  
  def remote_ip
    ip = request.remote_ip
    logger.info(ip)
    ip.gsub!(Regexp.union(Resolv::IPv4::Regex, Resolv::IPv6::Regex), '\0') || 'bogus address'
  end

  def render_rss(hide_description = false, limit = 15, start_date = nil, end_date = nil)
    if limit && !start_date && !end_date
      @pages_by_revision = @web.select.by_revision.first(limit)
    else
      @pages_by_revision = @web.select.by_revision
      @pages_by_revision.reject! { |page| page.revised_at < start_date } if start_date
      @pages_by_revision.reject! { |page| page.revised_at > end_date } if end_date
    end
    
    @hide_description = hide_description
    @link_action = @web.password ? 'published' : 'show'
    
    render :action => 'rss_feed'
  end

  def render_tex_web
    @web.select.by_name.inject({}) do |tex_web, page|
      tex_web[page.name] = RedClothForTex.new(page.content).to_tex
      tex_web
    end
  end

  def rss_with_content_allowed?
    @web.password.nil? or @web.published?
  end
  
  def truncate(text, length = 30, truncate_string = '...')
    if text.length > length then text[0..(length - 3)] + truncate_string else text end
  end
  
  def filter_spam(content)
    @@spam_patterns ||= load_spam_patterns
    @@spam_patterns.each do |pattern| 
      raise "Your edit was blocked by spam filtering" if content =~ pattern
    end
  end
  
  def load_spam_patterns
    spam_patterns_file = "#{RAILS_ROOT}/config/spam_patterns.txt"
    if File.exists?(spam_patterns_file)
      File.readlines(spam_patterns_file).inject([]) { |patterns, line| patterns << Regexp.new(line.chomp, Regexp::IGNORECASE) } 
    else
      []
    end
  end

  
end
