#
# waste.rb - »¶ºâÆüµ­Library
#
# Copyright (C) 2001, TADA Tadashi <sho@spc.gr.jp>
#

WASTE_VERSION = '0.9.2'

require 'pstore'
require 'cgi'
require 'lock'

#
# Integer
#
class Integer
	# adding comma each three digits.
	def to_disp_str
		self.to_s.reverse.gsub( /\d\d\d/, '\0,' ).reverse.sub( /^([-]{0,1}),/, '\1' )
	end
end

#
# CGI
#
class CGI
	def valid?( param, idx = 0 )
		self[param] and self[param][idx] and self[param][idx].length > 0
	end
end

#
# Time
#
class Time
	def rfc1123_date
		was_utc = self.utc?
		result = self.utc.strftime( '%a, %d %b %Y %X GMT' )
		self.localtime if not was_utc
		result
	end
end

#
# String
#
class String
	def make_link
		r = %r<(((http[s]{0,1}|ftp)://[\(\)%#!/0-9a-zA-Z_$@.&+-,'"*=;?:~-]+)|([0-9a-zA-Z_.-]+@[\(\)%!0-9a-zA-Z_$@.&+-,'"*-]+))>
		return self.
			gsub( ' ', "\001" ).
			gsub( '<', "\002" ).
			gsub( '>', "\003" ).
			gsub( '&', '&amp;' ).
			gsub( r ){ $1 == $2 ? "<a href=\"#$2\">#$2</a>" : "<a href=\"mailto:#$4\">#$4</a>" }.
			gsub( "\003", '&gt;' ).
			gsub( "\002", '&lt;' ).
			gsub( "\001", '&nbsp;' ).
			gsub( "\t", '&nbsp;' * 8 )
	end
end

#
# class Item
#
class Item
	attr_reader :name, :price, :note
	def initialize( name, price, note )
		raise ArgumentError::new( "Bad Item [#{name}]" ) if not name or name.length == 0
		raise ArgumentError::new( 'Bad price' ) if not price or price.to_i == 0
		@name = name
		@price = price.to_i
		@note = note
	end
end

#
# class Diary
#
class Diary
	attr_reader :name, :date, :note
	def initialize( name, date, note )
		raise ArgumentError::new( 'Bad name' ) if not name or name.length == 0
		raise ArgumentError::new( 'Bad date' ) if not date or /^\d\d\d\d\/\d\d\/\d\d$/ !~ date
		@name = name
		@date = date
		@note = note
		@items = []
	end

	def add( item )
		@items << item
	end

	def []( idx )
		@items[idx]
	end

	def each_item
		@items.each do |item|
			yield item
		end
	end

	def item_size
		@items.length
	end

	def total
		price = 0
		@items.each do |i|
			price += i.price
		end
		price
	end
end

#
# class Waste
#
class Waste
	attr_reader :year, :user, :edit, :person

	def Waste::version
		WASTE_VERSION
	end

	def version
		Waste::version
	end

	def initialize
		@cgi = CGI::new
		@diary = nil
		@user = @cgi.cookies['waste'][0] || ''
		@edit = nil
		@person = @cgi.valid?( 'person' ) ? @cgi['person'][0] : nil
		@waste = {}
		now = Time::now
		@last_modified = now.rfc1123_date

		if @cgi.valid?( 'edit' ) then # edit diary
			@edit = @cgi['edit'][0]
		elsif @cgi.valid?( 'name' ) # new entry then save data
			begin
				@diary = Diary::new( @cgi['name'][0], @cgi['date'][0], @cgi['note'][0] )
				1.upto( 30 ) do |i|
					if @cgi["item#{i}"][0] and @cgi["item#{i}"][0].length > 0 then
						item = Item::new( @cgi["item#{i}"][0], @cgi["price#{i}"][0], @cgi["note#{i}"][0] )
						@diary.add( item )
					end
				end
			rescue ArgumentError
			end
		end

		if @diary then
			@year = @diary.date[0,4]
			@month = @diary.date[5,2]
		elsif @edit
			@year = @edit[0,4]
			@month = @edit[5,2]
		elsif /^\d{6}$/ =~ @cgi['date'][0]
			@year = @cgi['date'][0][0,4]
			@month = @cgi['date'][0][4,2]
		elsif /^\d{4}$/ =~ @cgi['date'][0]
			@year = @cgi['date'][0]
			@month = '12'
		else
			@year = now.year.to_s
			@month = '12'
		end
		db = PStore::new( File::dirname( @cgi.path_translated ) + "/#{@year}.db" )
		Lock::lock( File::dirname( @cgi.path_translated ) + "/lock" ) do
			db.transaction do
				begin @waste = db['waste']; rescue; end
				begin @last_modified = db['last_modified']; rescue; end

				if @diary then
					key = @diary.date + @diary.name
					if @diary.item_size > 0 then # saving new data
						@waste[key] = @diary
					else # delete the data if exist
						@waste.delete( key )
					end
					db['waste'] = @waste
					db['last_modified'] = @last_modified = now.rfc1123_date
				end
			end
		end
	end

	def user?
		@user.empty? ? false : true
	end

	def header
		param = {
			'type' => 'text/html',
			'charset' => 'EUC-JP',
			'Last-Modified' => @last_modified,
		}
		if @cgi.valid?( 'name' ) then
			param['cookie'] = CGI::Cookie::new({
					'name' => 'waste',
					'value' => ["#{@cgi['name'][0]}"],
					'path' => File::dirname( @cgi.path_info ),
					'expires' => Time.now.gmtime + 24*60*60*365
			})
		end
		ERuby.noheader = true
		@cgi.header( param )
	end

	def diary( user, date )
		if @waste[date+user] then
			@waste[date+user]
		else
			Diary::new( user, date, '' )
		end
	end

	def each_year
		Dir::glob( File::dirname( @cgi.path_translated ) + "/????.db" ).each do |f|
			yield File::basename( f, '.db' )
		end
	end

	def each_diary( person = nil, limit = nil )
		re = person ? /^#{person}$/ : /.*/
		out = 0
		@waste.keys.sort.reverse_each do |date|
			if re =~ @waste[date].name then
				yield @waste[date]
				break if not person and limit and (out += 1) > limit
			end
		end
	end

	def each_month( times = 3 )
		if @waste.empty? then
			last = @month.to_i
		else
			last = @waste.keys.sort[-1][5,2].to_i
		end
		(last - times + 1).upto( last ) do |m|
			yield m if m > 0
		end
	end

	def each_total( month = 0 )
		re = (month == 0) ? /^#{@year}/ : /^#{@year}\/#{"%02d" % month}/

		users = Hash::new( 0 )
		each_diary do |diary|
			users[diary.name] += diary.total if re =~ diary.date
		end
		totals = {}
		users.each do |name,total|
			totals[total] = name
		end
		totals.keys.sort.reverse_each do |total|
			yield totals[total], total
		end
	end

	def each_user_total( user = @person )
		re = /^#{@year}\/(\d\d)\/\d\d#{user}$/
		months = Hash::new( 0 )
		each_diary do |diary|
			months[$1] += diary.total if re =~ (diary.date + diary.name)
		end
		months.keys.sort.each do |month|
			yield month, months[month]
		end
	end
end

