local InputContainer = require("ui/widget/container/inputcontainer")
local Screen = require("ui/screen")
local Geom = require("ui/geometry")
local Input = require("ui/input")
local GestureRange = require("ui/gesturerange")
local Device = require("ui/device")
local Event = require("ui/event")
local UIManager = require("ui/uimanager")
local Math = require("optmath")
local DEBUG = require("dbg")

local ReaderPaging = InputContainer:new{
	current_page = 0,
	number_of_pages = 0,
	last_pan_relative_y = 0,
	visible_area = nil,
	page_area = nil,
	show_overlap_enable = DSHOWOVERLAP,
	overlap = Screen:scaleByDPI(20),
	flip_steps = {0,1,2,5,10,20,50,100}
}

function ReaderPaging:init()
	if Device:hasKeyboard() then
		self.key_events = {
			GotoNextPage = {
				{Input.group.PgFwd}, doc = "go to next page",
				event = "GotoPageRel", args = 1 },
			GotoPrevPage = {
				{Input.group.PgBack}, doc = "go to previous page",
				event = "GotoPageRel", args = -1 },

			GotoFirst = {
				{"1"}, doc = "go to start", event = "GotoPercent", args = 0},
			Goto11 = {
				{"2"}, doc = "go to 11%", event = "GotoPercent", args = 11},
			Goto22 = {
				{"3"}, doc = "go to 22%", event = "GotoPercent", args = 22},
			Goto33 = {
				{"4"}, doc = "go to 33%", event = "GotoPercent", args = 33},
			Goto44 = {
				{"5"}, doc = "go to 44%", event = "GotoPercent", args = 44},
			Goto55 = {
				{"6"}, doc = "go to 55%", event = "GotoPercent", args = 55},
			Goto66 = {
				{"7"}, doc = "go to 66%", event = "GotoPercent", args = 66},
			Goto77 = {
				{"8"}, doc = "go to 77%", event = "GotoPercent", args = 77},
			Goto88 = {
				{"9"}, doc = "go to 88%", event = "GotoPercent", args = 88},
			GotoLast = {
				{"0"}, doc = "go to end", event = "GotoPercent", args = 100},
		}
	end
	self.number_of_pages = self.ui.document.info.number_of_pages
end

-- This method will  be called in onSetDimensions handler
function ReaderPaging:initGesListener()
	self.ges_events = {
		TapForward = {
			GestureRange:new{
				ges = "tap",
				range = Geom:new{
					x = Screen:getWidth()/4,
					y = Screen:getHeight()/4,
					w = 3*Screen:getWidth()/4,
					h = 2*Screen:getHeight()/3,
				}
			}
		},
		TapBackward = {
			GestureRange:new{
				ges = "tap",
				range = Geom:new{
					x = 0,
					y = Screen:getHeight()/4,
					w = Screen:getWidth()/4,
					h = 2*Screen:getHeight()/3,
				}
			}
		},
		ToggleFlipping = {
			GestureRange:new{
				ges = "tap",
				range = Geom:new{
					x = 0, y = 0,
					w = Screen:getWidth()/8,
					h = Screen:getHeight()/8
				}
			}
		},
		Swipe = {
			GestureRange:new{
				ges = "swipe",
				range = Geom:new{
					x = 0, y = 0,
					w = Screen:getWidth(),
					h = Screen:getHeight(),
				}
			}
		},
		TwoFingerSwipe = {
			GestureRange:new{
				ges = "two_finger_swipe",
				range = Geom:new{
					x = 0, y = 0,
					w = Screen:getWidth(),
					h = Screen:getHeight(),
				}
			}
		},
		Pan = {
			GestureRange:new{
				ges = "pan",
				range = Geom:new{
					x = 0, y = 0,
					w = Screen:getWidth(),
					h = Screen:getHeight(),
				},
				rate = 4.0,
			}
		},
		PanRelease = {
			GestureRange:new{
				ges = "pan_release",
				range = Geom:new{
					x = 0, y = 0,
					w = Screen:getWidth(),
					h = Screen:getHeight(),
				},
			}
		},
	}
end

function ReaderPaging:onReadSettings(config)
	self.page_positions = config:readSetting("page_positions") or {}
	self:gotoPage(config:readSetting("last_page") or 1)
	local soe = config:readSetting("show_overlap_enable")
	if not soe then
		self.show_overlap_enable = soe
	end
end

function ReaderPaging:onCloseDocument()
	self.ui.doc_settings:saveSetting("page_positions", self.page_positions)
	self.ui.doc_settings:saveSetting("last_page", self:getTopPage())
	self.ui.doc_settings:saveSetting("percent_finished", self.current_page/self.number_of_pages)
end

--[[
Set reading position on certain page
Page position is a fractional number ranging from 0 to 1, indicating the read percentage on
certain page. With the position information on each page whenever users change font size, 
page margin or line spacing or close and reopen the book, the page view will be roughly the same.
--]]
function ReaderPaging:setPagePosition(page, pos)
	DEBUG("set page position", pos)
	self.page_positions[page] = pos
end

--[[
Get reading position on certain page
--]]
function ReaderPaging:getPagePosition(page)
	-- Page number ought to be integer, somehow I notice that with 
	-- fractional page number the reader runs silently well, but the 
	-- number won't fit to retrieve page position. 
	page = math.floor(page)
	DEBUG("get page position", self.page_positions[page])
	return self.page_positions[page] or 0
end

function ReaderPaging:onTapForward()
	self:onPagingRel(1)
	return true
end

function ReaderPaging:onTapBackward()
	self:onPagingRel(-1)
	return true
end

function ReaderPaging:onToggleFlipping()
	self.view.flipping_visible = not self.view.flipping_visible
	self.flipping_mode = self.view.flipping_visible
	self.flipping_page = self.current_page
	if self.flipping_mode then
		self:updateOriginalPage(self.current_page)
	else
		self:updateOriginalPage(nil)
	end
	self.ui:handleEvent(Event:new("SetFlippingMode", self.flipping_mode))
	self.ui:handleEvent(Event:new("SetHinting", not self.flipping_mode))
	self.ui:handleEvent(Event:new("ReZoom"))
	UIManager:setDirty(self.view.dialog, "partial")
end

function ReaderPaging:updateOriginalPage(page)
	self.original_page = page
end

function ReaderPaging:updateFlippingPage(page)
	self.flipping_page = page
end

function ReaderPaging:flipping(flipping_page, flipping_ges)
	local whole = self.number_of_pages
	local steps = #self.flip_steps
	local stp_proportion = flipping_ges.distance / Screen:getWidth()
	local abs_proportion = flipping_ges.distance / Screen:getHeight()
	if flipping_ges.direction == "east" then
		self:gotoPage(flipping_page - self.flip_steps[math.ceil(steps*stp_proportion)])
	elseif flipping_ges.direction == "west" then
		self:gotoPage(flipping_page + self.flip_steps[math.ceil(steps*stp_proportion)])
	elseif flipping_ges.direction == "south" then
		self:gotoPage(flipping_page - math.floor(whole*abs_proportion))
	elseif flipping_ges.direction == "north" then
		self:gotoPage(flipping_page + math.floor(whole*abs_proportion))
	end
	UIManager:setDirty(self.view.dialog, "partial")
end

function ReaderPaging:onSwipe(arg, ges)
	if self.flipping_mode then
		self:flipping(self.flipping_page, ges)
		self:updateFlippingPage(self.current_page)
	elseif ges.direction == "west" then
		self:onPagingRel(1)
	elseif ges.direction == "east" then
		self:onPagingRel(-1)
	else
		UIManager.full_refresh = true
	end
	return true
end

function ReaderPaging:onTwoFingerSwipe(arg, ges)
	if self.original_page then
		self:gotoPage(self.original_page)
	end
end

function ReaderPaging:onPan(arg, ges)
	if self.flipping_mode then
		self:flipping(self.flipping_page, ges)
	elseif ges.direction == "north" or ges.direction == "south" then
		self:onPanningRel(self.last_pan_relative_y - ges.relative.y)
		self.last_pan_relative_y = ges.relative.y
	end
	return true
end

function ReaderPaging:onPanRelease(arg, ges)
	if self.flipping_mode then
		self:updateFlippingPage(self.current_page)
	else
		self.last_pan_relative_y = 0
		UIManager.full_refresh = true
	end
end

function ReaderPaging:onZoomModeUpdate(new_mode)
	-- we need to remember zoom mode to handle page turn event
	self.zoom_mode = new_mode
end

function ReaderPaging:onPageUpdate(new_page_no, orig)
	self.current_page = new_page_no
	if orig ~= "scrolling" then
		self.ui:handleEvent(Event:new("InitScrollPageStates", orig))
	end
end

function ReaderPaging:onViewRecalculate(visible_area, page_area)
	-- we need to remember areas to handle page turn event
	self.visible_area = visible_area:copy()
	self.page_area = page_area
end

function ReaderPaging:onGotoPercent(percent)
	DEBUG("goto document offset in percent:", percent)
	local dest = math.floor(self.number_of_pages * percent / 100)
	if dest < 1 then dest = 1 end
	if dest > self.number_of_pages then
		dest = self.number_of_pages
	end
	self:gotoPage(dest)
	return true
end

function ReaderPaging:onPagingRel(diff)
	if self.view.page_scroll then
		self:onScrollPageRel(diff)
	else
		self:onGotoPageRel(diff)
	end
	self:setPagePosition(self:getTopPage(), self:getTopPosition())
	return true
end

function ReaderPaging:onPanningRel(diff)
	if self.view.page_scroll then
		self:onScrollPanRel(diff)
	end
	self:setPagePosition(self:getTopPage(), self:getTopPosition())
	return true
end

--[[
Get read percentage on current page.
--]]
function ReaderPaging:getTopPosition()
	if self.view.page_scroll then
		local state = self.view.page_states[1]
		return (state.visible_area.y - state.page_area.y)/state.page_area.h
	else
		return 0
	end
end

--[[
Get page number of the page drawn at the very top part of the screen.
--]]
function ReaderPaging:getTopPage()
	if self.view.page_scroll then
		local state = self.view.page_states[1]
		return state.page
	else
		return self.current_page
	end
end

function ReaderPaging:onInitScrollPageStates(orig)
	DEBUG("init scroll page states", orig)
	if self.view.page_scroll then
		self.orig_page = self.current_page
		self.view.page_states = {}
		local blank_area = Geom:new{}
		blank_area:setSizeTo(self.view.dimen)
		while blank_area.h > 0 do
			local offset = Geom:new{}
			-- caculate position in current page
			if self.current_page == self.orig_page then
				local page_area = self.view:getPageArea(
					self.view.state.page,
					self.view.state.zoom,
					self.view.state.rotation)
				offset.y = page_area.h * self:getPagePosition(self.current_page)
			end
			local state = self:getNextPageState(blank_area, offset)
			--DEBUG("init new state", state)
			table.insert(self.view.page_states, state)
			if blank_area.h > 0 then
				blank_area.h = blank_area.h - self.view.page_gap.height
			end
			if blank_area.h > 0 then
				self:gotoPage(self.current_page + 1, "scrolling")
			end
		end
		self:gotoPage(self.orig_page, "scrolling")
	end
	return true
end

function ReaderPaging:onUpdateScrollPageRotation(rotation)
	for _, state in ipairs(self.view.page_states) do
		state.rotation = rotation
	end
	return true
end

function ReaderPaging:onUpdateScrollPageGamma(gamma)
	for _, state in ipairs(self.view.page_states) do
		state.gamma = gamma
	end
	return true
end

function ReaderPaging:getNextPageState(blank_area, offset)
	local page_area = self.view:getPageArea(
		self.view.state.page,
		self.view.state.zoom,
		self.view.state.rotation)
	local visible_area = Geom:new{x = 0, y = 0}
	visible_area.w, visible_area.h = blank_area.w, blank_area.h
	visible_area.x, visible_area.y = page_area.x, page_area.y
	visible_area = visible_area:shrinkInside(page_area, offset.x, offset.y)
	-- shrink blank area by the height of visible area
	blank_area.h = blank_area.h - visible_area.h
	return {
		page = self.view.state.page,
		zoom = self.view.state.zoom,
		rotation = self.view.state.rotation,
		gamma = self.view.state.gamma,
		offset = Geom:new{ x = self.view.state.offset.x, y = 0},
		visible_area = visible_area,
		page_area = page_area,
	}
end

function ReaderPaging:getPrevPageState(blank_area, offset)
	local page_area = self.view:getPageArea(
		self.view.state.page,
		self.view.state.zoom,
		self.view.state.rotation)
	local visible_area = Geom:new{x = 0, y = 0}
	visible_area.w, visible_area.h = blank_area.w, blank_area.h
	visible_area.x = page_area.x
	visible_area.y = page_area.y + page_area.h - visible_area.h
	visible_area = visible_area:shrinkInside(page_area, offset.x, offset.y)
	-- shrink blank area by the height of visible area
	blank_area.h = blank_area.h - visible_area.h
	return {
		page = self.view.state.page,
		zoom = self.view.state.zoom,
		rotation = self.view.state.rotation,
		gamma = self.view.state.gamma,
		offset = Geom:new{ x = self.view.state.offset.x, y = 0},
		visible_area = visible_area,
		page_area = page_area,
	}
end

function ReaderPaging:updateTopPageState(state, blank_area, offset)
	local visible_area = Geom:new{x = 0, y = 0}
	visible_area.w, visible_area.h = blank_area.w, blank_area.h
	visible_area.x, visible_area.y = state.visible_area.x, state.visible_area.y
	if state.page == self.number_of_pages then
		visible_area:offsetWithin(state.page_area, offset.x, offset.y)
	else
		visible_area = visible_area:shrinkInside(state.page_area, offset.x, offset.y)
	end
	-- shrink blank area by the height of visible area
	blank_area.h = blank_area.h - visible_area.h
	state.visible_area = visible_area
	return state
end

function ReaderPaging:updateBottomPageState(state, blank_area, offset)
	local visible_area = Geom:new{x = 0, y = 0}
	visible_area.w, visible_area.h = blank_area.w, blank_area.h
	visible_area.x = state.page_area.x
	visible_area.y = state.visible_area.y + state.visible_area.h - visible_area.h
	if state.page == 1 then
		visible_area:offsetWithin(state.page_area, offset.x, offset.y)
	else
		visible_area = visible_area:shrinkInside(state.page_area, offset.x, offset.y)
	end
	-- shrink blank area by the height of visible area
	blank_area.h = blank_area.h - visible_area.h
	state.visible_area = visible_area
	return state
end

function ReaderPaging:genPageStatesFromTop(top_page_state, blank_area, offset)
	-- Offset should always be greater than 0
	-- otherwise if offset is less than 0 the height of blank area will be
	-- larger than 0 even if page area is much larger than visible area,
	-- which will trigger the drawing of next page leaving part of current
	-- page undrawn. This should also be true for generating from bottom.
	if offset.y < 0 then offset.y = 0 end
	local state = self:updateTopPageState(top_page_state, blank_area, offset)
	--DEBUG("updated state", state)
	local page_states = {}
	if state.visible_area.h > 0 then
		table.insert(page_states, state)
	end
	--DEBUG("blank area", blank_area)
	local current_page = state.page
	while blank_area.h > 0 do
		blank_area.h = blank_area.h - self.view.page_gap.height
		if blank_area.h > 0 then
			if self.current_page == self.number_of_pages then break end
			self:gotoPage(current_page + 1, "scrolling")
			current_page = current_page + 1
			local state = self:getNextPageState(blank_area, Geom:new{})
			--DEBUG("new state", state)
			table.insert(page_states, state)
		end
	end
	return page_states
end

function ReaderPaging:genPageStatesFromBottom(bottom_page_state, blank_area, offset)
	-- scroll up offset should always be less than 0
	if offset.y > 0 then offset.y = 0 end
	local state = self:updateBottomPageState(bottom_page_state, blank_area, offset)
	--DEBUG("updated state", state)
	local page_states = {}
	if state.visible_area.h > 0 then
		table.insert(page_states, state)
	end
	--DEBUG("blank area", blank_area)
	local current_page = state.page
	while blank_area.h > 0 do
		blank_area.h = blank_area.h - self.view.page_gap.height
		if blank_area.h > 0 then
			if self.current_page == 1 then break end
			self:gotoPage(current_page - 1, "scrolling")
			current_page = current_page - 1
			local state = self:getPrevPageState(blank_area, Geom:new{})
			--DEBUG("new state", state)
			table.insert(page_states, 1, state)
		end
	end
	return page_states
end

function ReaderPaging:onScrollPanRel(diff)
	DEBUG("pan relative height:", diff)
	local blank_area = Geom:new{}
	blank_area:setSizeTo(self.view.dimen)
	if diff > 0 then
		local first_page_state = table.remove(self.view.page_states, 1)
		local offset = Geom:new{
			x = 0,
			y = diff,
		}
		self.view.page_states = self:genPageStatesFromTop(first_page_state, blank_area, offset)
	end
	if diff < 0 then
		local last_page_state = table.remove(self.view.page_states)
		local offset = Geom:new{
			x = 0,
			y = diff
		}
		self.view.page_states = self:genPageStatesFromBottom(last_page_state, blank_area, offset)
	end
	-- update current pageno to the very last part in current view
	self:gotoPage(self.view.page_states[#self.view.page_states].page, "scrolling")
	
	UIManager:setDirty(self.view.dialog, "fast")
end

function ReaderPaging:onScrollPageRel(diff)
	DEBUG("scroll relative page:", diff)
	local blank_area = Geom:new{}
	blank_area:setSizeTo(self.view.dimen)
	if diff > 0 then
		local last_page_state = table.remove(self.view.page_states)
		local offset = Geom:new{
			x = 0,
			y = last_page_state.visible_area.h - self.overlap
		}
		self.view.page_states = self:genPageStatesFromTop(last_page_state, blank_area, offset)
	end
	if diff < 0 then
		local first_page_state = table.remove(self.view.page_states, 1)
		local offset = Geom:new{
			x = 0,
			y = -first_page_state.visible_area.h + self.overlap
		}
		self.view.page_states = self:genPageStatesFromBottom(first_page_state, blank_area, offset)
	end
	-- update current pageno to the very last part in current view
	self:gotoPage(self.view.page_states[#self.view.page_states].page, "scrolling")
	UIManager:setDirty(self.view.dialog)
end

function ReaderPaging:onGotoPageRel(diff)
	DEBUG("goto relative page:", diff)
	local new_va = self.visible_area:copy()
	local x_pan_off, y_pan_off = 0, 0

	if self.zoom_mode:find("width") then
		y_pan_off = self.visible_area.h * diff
	elseif self.zoom_mode:find("height") then
		x_pan_off = self.visible_area.w * diff
	else
		-- must be fit content or page zoom mode
		if self.visible_area.w == self.page_area.w then
			y_pan_off = self.visible_area.h * diff
		else
			x_pan_off = self.visible_area.w * diff
		end
	end
	-- adjust offset to help with page turn decision
	-- we dont take overlap into account here yet, otherwise new_va will
	-- always intersect with page_area
	x_pan_off = Math.roundAwayFromZero(x_pan_off)
	y_pan_off = Math.roundAwayFromZero(y_pan_off)
	new_va.x = Math.roundAwayFromZero(self.visible_area.x+x_pan_off)
	new_va.y = Math.roundAwayFromZero(self.visible_area.y+y_pan_off)

	if new_va:notIntersectWith(self.page_area) then
		-- view area out of page area, do a page turn
		self:gotoPage(self.current_page + diff)
		-- if we are going back to previous page, reset
		-- view area to bottom of previous page
		if x_pan_off < 0 then
			self.view:PanningUpdate(self.page_area.w, 0)
		elseif y_pan_off < 0 then
			self.view:PanningUpdate(0, self.page_area.h)
		end
		-- reset dim_area
		--self.view.dim_area.h = 0
		--self.view.dim_area.w = 0
		--
	else
		-- not end of page yet, goto next view
		-- adjust panning step according to overlap
		if self.show_overlap_enable then
			if x_pan_off > self.overlap then
				-- moving to next view, move view
				x_pan_off = x_pan_off - self.overlap
			elseif x_pan_off < -self.overlap then
				x_pan_off = x_pan_off + self.overlap
			end
			if y_pan_off > self.overlap then
				y_pan_off = y_pan_off - self.overlap
			elseif y_pan_off < -self.overlap then
				y_pan_off = y_pan_off + self.overlap
			end
			-- we have to calculate again to count into overlap
			new_va.x = Math.roundAwayFromZero(self.visible_area.x+x_pan_off)
			new_va.y = Math.roundAwayFromZero(self.visible_area.y+y_pan_off)
		end
		-- fit new view area into page area
		new_va:offsetWithin(self.page_area, 0, 0)
		-- calculate panning offsets
		local panned_x = new_va.x - self.visible_area.x
		local panned_y = new_va.y - self.visible_area.y
		-- adjust for crazy float point overflow...
		if math.abs(panned_x) < 1 then
			panned_x = 0
		end
		if math.abs(panned_y) < 1 then
			panned_y = 0
		end
		-- singal panning update
		self.view:PanningUpdate(panned_x, panned_y)
		-- update dime area in ReaderView
		if self.show_overlap_enable then
			self.view.dim_area.h = new_va.h - math.abs(panned_y)
			self.view.dim_area.w = new_va.w - math.abs(panned_x)
			if panned_y < 0 then
				self.view.dim_area.y = new_va.y - panned_y
			else
				self.view.dim_area.y = 0
			end
			if panned_x < 0 then
				self.view.dim_area.x = new_va.x - panned_x
			else
				self.view.dim_area.x = 0
			end
		end
		-- update self.visible_area
		self.visible_area = new_va
	end

	return true
end

function ReaderPaging:onRedrawCurrentPage()
	self.ui:handleEvent(Event:new("PageUpdate", self.current_page))
	return true
end

function ReaderPaging:onSetDimensions()
	-- update listening according to new screen dimen
	if Device:isTouchDevice() then
		self:initGesListener()
	end
end

-- wrapper for bounds checking
function ReaderPaging:gotoPage(number, orig)
	if number == self.current_page then
		return true
	end
	if number > self.number_of_pages
	or number < 1 then
		DEBUG("wrong page number: "..number.."!")
		return false
	end
	DEBUG("going to page number", number)

	-- this is an event to allow other controllers to be aware of this change
	self.ui:handleEvent(Event:new("PageUpdate", number, orig))
	return true
end

function ReaderPaging:onGotoPage(number)
	self:gotoPage(number)
	return true
end

return ReaderPaging
