# $Id: page.rb,v 1.52 2004/04/22 14:47:49 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 'pathname'
require 'qwik/util'
require 'qwik/config'

module Qwik
  class PageExistError < StandardError; end

  class PageKey < String
    def initialize(k) # input utf-8 string
      super(k.to_s)
    end

    def inspect
      "#<PageKey:"+self+">"
    end

    def encode
      if self =~ /[_0-9A-Za-z]+/
	self
      else
	self.escape
      end
    end

    def PageKey.decode(encoded_key)
      encoded_key = encoded_key.to_s
      if encoded_key.include?('%')
	PageKey.new(encoded_key.unescape) # adhoc
      else
	PageKey.new(encoded_key)
      end
    end
  end

  class Pages
    include Enumerable

    FIRST_PAGE_ID = "1"

    def initialize(config, dir)
      @config = config
      @dir = dir.path
      @backup_path = @dir+".backup"
      @backup_path.mkdir if !@backup_path.directory?
      @pages = {}
    end
    attr_reader :backup_path

    def to_key(k)
      return k if k.is_a? PageKey
      PageKey.new(k)
    end
    private :to_key

    def path(k)  @dir+to_key(k).encode;  end
    def spath(k) @config.super_dir.path+to_key(k).encode; end

    def create(k) # create new page
      raise PageExistError if baseexist?(k)
      @pages[k] = Page.new(@config, self, k, path(k))
    end

    def create_new
      create(get_new_id())
    end

    def [](k)
      return @pages[k] if @pages[k]
      return @pages[k] = Page.new(@config, self, k, path(k)) if baseexist?(k)
      return @pages[k] = Page.new(@config, self, k, path(k), spath(k)) if spath(k).exist?
      nil
    end

    def exist?(k)
      @pages[k] != nil || path(k).exist? || spath(k).exist?
    end

    def baseexist?(k)
      path(k).exist?
    end

    def delete(k)
      self[k].delete
      @pages[k] = nil
    end

    def each()
      Dir.foreach(@dir.to_s) {|file|
	f = (@dir+file).path
	next if ! f.file?
	base = f.basename.to_s
	next if base =~ /^,/
	next if base =~ /^_/
	next if base =~ /\./
	next if base =~ /~$/
	k = PageKey.decode(f.basename)
	page = self[k]
	next if page.nil?
	yield(page)
      }
    end

    def keys()
      map {|page| page.key }
    end

    def title_list
      sort_by {|page|
	page.get_title.downcase
      }
    end

    def date_list()
      sort_by {|page|
	page.mtime
      }
    end

    def find_title(title)
      find {|page|
	page.get_title == title
      }
    end

    def get_new_id()
      numkeys = keys.map {|k| k =~ /^[0-9]+$/ ? k.to_i : 0 }
      return (numkeys.max + 1).to_s if numkeys.max
      return FIRST_PAGE_ID
    end
  end

  class PageCollisionError < StandardError; end

  class Page
    def initialize(config, pages, key, file, sfile=nil)
      @config = config
      @pages = pages
      @key = key.is_a?(PageKey) ? key : PageKey.new(key)
      @backup_path = @pages.backup_path
      @file = file
      @sfile = sfile
      touch(@file) if (!@sfile || !@sfile.exist?) && !@file.exist?
    end
    attr_reader :key

    def inspect
      "#<Page:"+@key+">"
    end

    def url
      @key.encode+".html"
    end

    def mtime()
      return @file.mtime if @file.exist?
      return @sfile.mtime if @sfile && @sfile.exist?
      return Time.now # for now
    end

    def store(v, md5=nil)
      v = v.u8tosjis
      store_backup(v)
      raise PageCollisionError if md5 && load().md5hex != md5
      @file.open('wb'){|f| f.print(v) }
      nil
    end

    def store_backup(v)
      backup = @backup_path+(Time.now.to_i.to_s+'_'+@file.basename)
      backup.open('wb'){|f| f.print(v) }
    end

    def append(v, md5=nil)
      store(load() + v, md5)
    end

    def delete()
      @file.unlink
    end

    def load()
      str = ""
      if @file.exist?
	str = @file.open{|f| f.read}
      elsif @sfile && @sfile.exist?
	str = @sfile.open{|f| f.read}
      end
      str.sjistou8
    end

    def get_title
      base = @file.basename.to_s # at the first, get file name
      return base unless base =~ /^[0-9]+$/u || base =~ /^_/u # only numbers? or begin with _ ?
      line = first_line() # get first line
      return base unless line =~ /^\*(.+)$/u # the line does not begin with * ?
      title_base = $1
      return base if title_base =~ /^\*/u # only h2 level
      title_base.sub!(/^\s+/u, '') # remove first and last spaces
      title_base.sub!(/\s+$/u, '')
      return title_base
    end

    private
    def first_line
      load().each_line {|line|
	next if line =~ /^#/
	return line.chomp
      }
      ""
    end

    def touch(path) # ref. fileutils.rb
      begin
	t = Time.now
	path.utime(t, t)
      rescue Errno::ENOENT
        path.open('a') { }
      end
    end
  end
end
