# $Id: command.rb,v 1.23 2004/05/12 18:26:04 eto Exp $
# Copyright (C) 2003-2004 Kouichirou Eto, All rights reserved.
# This is free software with ABSOLUTELY NO WARRANTY.
# You can redistribute it and/or modify it under the terms of the GNU GPL2.

require "qwik/parser"
require "qwik/util"
require "qwik/site"
require "qwik/diff"
require "qwik/generator"
require "qwik/extension"
require "qwik/ext_diff"

module Qwik
  class Command
    def initialize(config, memory, site, user, cmd, pagename, query)
      @config, @memory = config, memory
      @site, @user, @cmd, @pagename, @query = site, user, cmd, pagename, query
      @time = Time.now
    end
    attr_reader :config, :memory
    attr_reader :site, :user, :cmd, :pagename, :query

    def page() @site[@pagename]; end

    def time
      sprintf("%.6f", @time.to_i + @time.usec/1000000.0)
    end
  end

  class CommandDispatcher
    def initialize(session)
      @session = session
    end

    def dispatch
      c = generate_command
      set_plugin(c)

      e = nil
      case c.cmd
      when "view"
	e = ViewCommand.new(c)
      when "edit"
	e = EditCommand.new(c)
      when "save"
	e = SaveCommand.new(c)
      when "new"
	e = NewCommand.new(c)
      else
	e = UnknownCommand.new(c)
      end
      e.execute if e
    end

    private

    def generate_command
      config   = @session.config
      memory   = @session.memory
      site     = @session.site
      r        = @session.req
      user     = r.user
      pagename = r.base
      query    = r.query

      cmd = "view"
      cmd = query["c"] if query["c"]
      cmd = "new" if pagename == "_new"

      Command.new(config, memory, site, user, cmd, pagename, query)
    end

    def set_plugin(command)
      options = Hash.new("")
      options["config"]   = @session.config
      options["memory"]   = @session.memory
      options["req"]      = @session.req
      options["res"]      = @session.res
      options["command"]  = command
      options["site"]     = command.site
      options["user"]     = command.user
      options["pagename"] = command.pagename
      options["page"]     = command.page
      options["query"]    = command.query
      command.site.plugin = Plugin.new(options)
    end
  end

  class GeneralCommand
    def initialize(command)
      @command = command
      @config   = @command.config
      @memory   = @command.memory
      @user     = @command.user
      @query    = @command.query
      @site     = @command.site
      @pagename = @command.pagename
      @page     = @command.page
      @g = Qxhtml.new
      @data = {}
    end

    private
    def message_gen(title, msg)
      gen = PageGenerator.new(@config, @memory)
      gen.template = "message"
      gen.head << @g.make(:title){title}
      gen.head << @g.stylesheet("/theme/base.css")
      gen.head << @g.stylesheet(theme_path("qwiksystem"))
      @data[:view_title] = title
      @data[:msg]        = msg
      @data[:footer]     = @site["SiteFooter"].load.w.res(@site)
      gen.update(@data)
      gen
    end

    def message(title, msg)
      gen = message_gen(title, msg)
      gen.generate
    end

    def redirect(url, title, msg)
      gen = message_gen(title, msg)
      gen.head << @g.refresh(1, url)
      gen.generate
    end

    def theme_path(t)
      "/theme/#{t}/#{t}.css"
    end
  end

  class ViewCommand < GeneralCommand
    def execute
      return page_not_found() if @page.nil?
      gen = PageGenerator.new(@config, @memory)
      gen.head << @g.make(:title){@site.title(@pagename)}
      gen.head << @g.stylesheet("/theme/base.css")
      gen.head << @g.stylesheet(theme_path(theme))
      @data[:topmenu]    = @site["TopMenu"].load.w.res(@site)
      @data[:view_title] = @page.get_title
#     @data[:body_enter] = nil
#     @data[:toc]        = nil
      @data[:body]       = @page.load.w.tdiary_res(@site)
      @data[:page_attribute] = @site["PageAttribute"].load.w.res(@site)
#     @data[:body_leave] = nil
      @data[:sidemenu]   = @site["SideMenu"].load.w.res(@site)
      @data[:footer]     = @site["SiteFooter"].load.w.res(@site)
      gen.update(@data)
      gen.generate
    end

    private
    def theme
      t = @site.siteconfig["theme"]
      return t if t
      @config.default_theme
    end

    def page_not_found
      message("ページが見つかりません", "ページが見つかりません．")
    end
  end

  class EditCommand < GeneralCommand
    def execute
      cmd_edit()
    end

    def cmd_edit(contents=nil, msg=nil, differ=nil)
      gen = PageGenerator.new(@config, @memory)
      gen.template = "edit"
      gen.head << @g.make(:title){@site.title(@pagename)}
      gen.head << @g.stylesheet("/theme/base.css")
      gen.head << @g.stylesheet(theme_path("qwiksystem"))
      gen.head << @g.meta(:name=>"ROBOTS", :content=>"NOINDEX,NOFOLLOW")
      @data[:view_title]   = @site.title(@pagename)
      @data[:msg]          = msg if msg
      @data[:differ]       = differ
      @data[:formupdate]   = REXML::Attr.new(:action => @pagename+".html")
      str = contents ? contents : @page.load
      @data[:md5hex]       = REXML::Attr.new(:value => str.md5hex)
      @data[:contents]     = str
      @data[:editorfooter] = @site["EditorFooter"].load.w.res(@site)
#     <div id="link">最新版を参照: [<span id="linkp"></span>]</div>
      @data[:footer]       = @site["SiteFooter"].load.w.res(@site)
      gen.update(@data)
      gen.generate
    end

    private
    def page_error
      message("ページ生成エラー", "サーバ内でエラーが発生しました。よろしければ、メールにて状況報告をお願いします。")
    end
  end

  class SaveCommand < EditCommand
    def execute
      contents = @query["contents"]

      return page_error if contents.nil?

      # cannot delete a page with password
      if contents.size == 0 && !@page.have_password?
	return page_delete()
      end

      newcontents = @page.embed_password(contents) # embed the password
      if !@page.match_password?(newcontents)
	return cmd_edit(contents, "パスワードが一致しませんでした。")
      end
      contents = newcontents

      contents = contents.gsub(/\r/, "")
      md5hex = @query["md5hex"]
      mf5hex = nil if @page.have_password?
      begin
	@page.store(contents, md5hex) # STORE
	@site.log(@command.time, @user, "save", @pagename) # STORE
      rescue PageCollisionError # failed to save?
	msg = @g.div{[
	    @g.p{"更新が衝突しました。"},
	    @g.p{["下記の内容をテキストエディタなどに保存し、",
		@g.a(@pagename+".html"){"最新のページ"},
		"を参照後に再編集してください。"]}
	  ]}
	return page_conflict(contents, msg)
      end

      msg = @g.div{[
	  @g.p{"ページを保存しました。"},
	  @g.p{[@g.a(@pagename+".html"){"最新のページ"}, "に自動的に飛びます。"]}
	]}
      redirect(@pagename+".html", "ページを保存しました", msg)
    end

    def page_conflict(contents=nil, msg=nil)
      differ = DiffHtmlGenerator.new(contents, @page.load).to_rexml
      cmd_edit(contents, msg, differ)
    end

    def page_delete()
      @site.delete(@pagename) # DELETE
      @site.log(@command.time, @user, "delete", @pagename) # DELETE

      msg = @g.div{[
	  @g.p{"ページを削除しました。"},
	  @g.p{[@g.a(@config.frontpage+".html"){"トップページ"}, "に戻る。"]}
	]}
      message("ページを削除しました", msg)
    end
  end

  class NewCommand < GeneralCommand
    def execute
      frompagename = @query["f"] # stands for "from"
      newpagetitle = @query["t"]
      page = @site.create_new # CREATE
      newpagename = page.key
      @site.log(@command.time, @user, "create", newpagename) # CREATE

      if newpagetitle
	page.store("* #{newpagetitle}\n") # at the first, make the page with the title.
	if frompagename
	  str = @site[frompagename].load # change the from page
	  if str =~ /\[\[(.+)\|#{newpagetitle}\]\]/
	    str.gsub!(/\[\[(.+)\|#{newpagetitle}\]\]/){|g|
	      "[[#{$1}|#{newpagename}]]"
	    }
	  else
	    str.gsub!(/\[\[#{newpagetitle}\]\]/, "[[#{newpagetitle}|#{newpagename}]]")
	  end
	  begin
	    @site[frompagename].store(str) # STORE
	  rescue PageCollisionError # failed to save?
	    # FIX: do nothing
	  end
	end
      else
	page.store("") # make a null page
      end

      newpagehref = newpagename+".html?c=edit"

      msg = @g.div{[
	  @g.p{"新規ページを作成しました"},
	  @g.p{[@g.a(newpagehref){"新規ページ編集画面"}, "に自動的に飛びます。"]}
	]}
      redirect(newpagehref, "新規ページ作成", msg)
    end
  end

  class UnknownCommand < GeneralCommand
    def execute
      message("不明なコマンド", "不明なコマンドが実行されました。謎。")
    end
  end

end
