# -*- coding:utf-8 -*-
# memo_stack.rb

require 'fileutils'

class MemoStack
  def MemoStack.memo_stack(kind, opts={})
    case kind
    when :local then LocalMemoStack.new(opts)
    else
      raise "Unknown kind -- #{kind}"
    end
  end
  
  def create_memo
    Memo.new(self)
  end
end

class MemoStack::Memo
  attr_reader :stack, :memo_id
  
  def initialize(stack, memo_id=nil)
    @stack = stack
    @memo_id = memo_id
  end
  
  def update!(params={})
    @stack.save(self, params) # memo_id が付けられる
  end
  
  def data
    @stack.data_of(self)
  end
  
  def kind
    @stack.kind_of(self).to_sym
  end
end
  
class LocalMemoStack < MemoStack
  attr_reader :basepath

  def initialize(opts={})
    if opts.has_key? :path
    then  path = opts[:path]
    else  raise ArgumentError, "required path"
    end
    if File.exist?(path)
      if FileTest.directory?(path)
      then  @basepath = path
      else  raise "the specified path is not unsupported MemoStack type"
      end
    else
      FileUtils.mkdir_p(path)
      @basepath = path
    end
  end

  def last_created
    path = all_path.sort_by {|i| File.ctime(i) }.last
    memo_id = File.basename(path).split('.').first
    Memo.new(self, memo_id)
  end

  def save(memo, params)
    data = params[:data] ; raise "requrie data" if data.nil?
    kind = params[:kind] ; raise "requrie kind" if kind.nil?
    mid = memo.memo_id || __timestamp__
    path = File.join(@basepath, "#{mid}.#{kind}")
    File.open(path, "w") { |io| io.write(data) }
    if memo.memo_id.nil? then
      memo.instance_eval { @memo_id = mid }
    end
  end

  def data_of memo
    path = __path_to_memo__(memo.memo_id)
    File.read(path)
  end

  def kind_of memo
    path = __path_to_memo__(memo.memo_id)
    path.split('.').last
  end

  private

  def all_path
    Dir[File.join(@basepath, "*")].find_all {|i| FileTest.file?(i) }
  end

  def __path_to_memo__(memo_id)
    candidates = Dir[File.join(@basepath, "#{memo_id}.*")]
    candidates.sort_by{|i| File.stat(i).mtime }.last
  end

  def __timestamp__
    ts = Time.now.to_f
    while __path_to_memo__(ts)
      ts = Time.now.to_f
    end
    ts
  end

end

def MemoStack(kind, opts={})
  MemoStack.memo_stack(kind, opts)
end


if __FILE__ == $0 then
  require 'test/unit'
  
  class TestMemoStack < Test::Unit::TestCase
    
    def setup
      @stack_path = "/tmp/hogehoge.memostack"
      @stack = MemoStack(:local, :path=>"/tmp/hogehoge.memostack")
    end
    
    def teardown
      remove_local_stack(@stack_path)
    end
    
    # ローカルディスク上にメモスタックを新規生成
    def test_create_local_memo_stack
      stack_path = "/tmp/#{Time.now.to_f.to_s}.memostack"
      
      assert_equal( false, 
                    File.exist?(stack_path),
                    'メモスタックは存在しない')
      
      assert_kind_of( MemoStack, 
                      MemoStack(:local, :path=>stack_path),
                      '新規メモスタックを生成')
      
      assert_equal( true, 
                    File.exist?(stack_path),
                    'メモスタックが新しく作られた')
    ensure
      remove_local_stack(stack_path) # 後始末
    end
    
    # ローカルディスク上の既存のメモスタック
    def test_create_local_memo_stack
      stack_path = "/tmp/#{Time.now.to_f.to_s}.memostack"
      MemoStack(:local, :path=>stack_path)
      
      assert_equal( true, 
                    File.exist?(stack_path),
                    'メモスタックが存在')
      
      assert_kind_of( MemoStack, 
                      MemoStack(:local, :path=>stack_path),
                      '既存のメモスタック')
    ensure
      remove_local_stack(stack_path) # 後始末
    end
    
    
    def test_create_memo
      assert_nothing_raised { @stack.create_memo }
    end
    
    def test_memo_update!
      memo = @stack.create_memo
      data = "hello, world"
      kind = :txt
      assert_nothing_raised {
        memo.update!(:data=>data, :kind=>kind)
      }
      assert_equal(data, memo.data)
      assert_equal(kind, memo.kind)
    end
    
    def test_last_created
      memo = @stack.create_memo
      data = "hello, world"
      kind = :txt
      memo.update!(:data=>data, :kind=>kind)
      assert_nothing_raised { memo = @stack.last_created }
      assert_equal(data, memo.data)
      assert_equal(kind, memo.kind)
    end
    
    private
    def remove_local_stack(path)
      FileUtils.rm_rf(path)
    end
  end
end