require "zip"
require "mime"
require "json"

class Image
	property data : Bytes
	property mime : String
	property filename : String
	property size : Int32
	def initialize(@data, @mime, @filename, @size)
	end
end

class Entry
	property zip_path : String
	property book_title : String
	property title : String
	property size : String
	property pages : Int32
	property cover_url : String

	JSON.mapping zip_path: String, book_title: String, title: String, \
		size: String, pages: Int32, cover_url: String

	def initialize(path, @book_title)
		@zip_path = path
		@title = File.basename path, ".zip"
		@size = (File.size path).humanize_bytes
		@pages = Zip::File.new(path).entries
			.select { |e|
				["image/jpeg", "image/png"].includes? \
				MIME.from_filename? e.filename
			}
			.size
		@cover_url = "/api/page/#{@book_title}/#{title}/1"
	end
	def read_page(page_num)
		Zip::File.open @zip_path do |file|
			page = file.entries
				.select { |e|
					["image/jpeg", "image/png"].includes? \
					MIME.from_filename? e.filename
				}
				.sort { |a, b| a.filename <=> b.filename }
				.[page_num - 1]
			page.open do |io|
				slice = Bytes.new page.uncompressed_size
				bytes_read = io.read_fully? slice
				unless bytes_read
					return nil
				end
				return Image.new slice, MIME.from_filename(page.filename),\
					page.filename, bytes_read
			end
		end
	end
end

class Title
	JSON.mapping dir: String, entries: Array(Entry), title: String

	def initialize(dir : String)
		@dir = dir
		@title = File.basename dir
		@entries = (Dir.entries dir)
			.select { |path| (File.extname path) == ".zip" }
			.map { |path| Entry.new File.join(dir, path), @title }
			.select { |e| e.pages > 0 }
			.sort { |a, b| a.title <=> b.title }
	end
	def get_entry(name)
		@entries.find { |e| e.title == name }
	end
	def save_progress(username, entry, page)
		info = TitleInfo.new @dir
		if info.progress[username]?.nil?
			info.progress[username] = {entry => page}
			info.save @dir
			return
		end
		info.progress[username][entry] = page
		info.save @dir
	end
	def load_progress(username, entry : String)
		info = TitleInfo.new @dir
		if info.progress[username]?.nil?
			return 0
		end
		if info.progress[username][entry]?.nil?
			return 0
		end
		info.progress[username][entry]
	end
	def load_percetage(username, entry : String)
		info = TitleInfo.new @dir
		page = load_progress username, entry
		entry_obj = get_entry entry
		return 0 if entry_obj.nil?
		page / entry_obj.pages
	end
	def load_percetage(username)
		read_pages = total_pages = 0
		@entries.each do |e|
			read_pages += load_progress username, e.title
			total_pages += e.pages
		end
		read_pages / total_pages
	end
	def next_entry(current_entry_obj)
		idx = @entries.index current_entry_obj
		return nil if idx.nil? || idx == @entries.size - 1
		@entries[idx + 1]
	end
end

class TitleInfo
	# { user1: { entry1: 10, entry2: 0 } }
	include JSON::Serializable

	@[JSON::Field(key: "comment")]
	property comment = "Generated by Mango. DO NOT EDIT!"

	@[JSON::Field(key: "progress")]
	property progress : Hash(String, Hash(String, Int32))

	def initialize(title_dir)
		info = nil

		json_path = File.join title_dir, "info.json"
		if File.exists? json_path
			info = TitleInfo.from_json File.read json_path
		else
			info = TitleInfo.from_json "{\"progress\": {}}"
		end

		@progress = info.progress.clone
	end
	def save(title_dir)
		json_path = File.join title_dir, "info.json"
		File.write json_path, self.to_pretty_json
	end
end

class Library
	JSON.mapping dir: String, titles: Array(Title)

	def initialize(dir : String)
		@dir = dir
		unless Dir.exists? dir
			Dir.mkdir_p dir
		end
		@titles = (Dir.entries dir)
			.select { |path| File.directory? File.join dir, path }
			.map { |path| Title.new File.join dir, path }
			.select { |title| !title.entries.empty? }
	end
	def get_title(name)
		@titles.find { |t| t.title == name }
	end
end
