local title = {}
local php
local NS_MEDIA = -2

local util = require 'libraryUtil'
local checkType = util.checkType
local checkTypeForIndex = util.checkTypeForIndex

local function checkNamespace( name, argIdx, arg )
	if type( arg ) == 'string' and tostring( tonumber( arg ) ) == arg then
		arg = tonumber( arg )
	end
	if type( arg ) == 'number' then
		arg = math.floor( arg + 0.5 )
		if not mw.site.namespaces[arg] then
			local msg = string.format( "bad argument #%d to '%s' (unrecognized namespace number '%s')",
				argIdx, name, arg
			)
			error( msg, 3 )
		end
	elseif type( arg ) == 'string' then
		local ns = mw.site.namespaces[arg]
		if not ns then
			local msg = string.format( "bad argument #%d to '%s' (unrecognized namespace name '%s')",
				argIdx, name, arg
			)
			error( msg, 3 )
		end
		arg = ns.id
	else
		local msg = string.format( "bad argument #%d to '%s' (string or number expected, got %s)",
			argIdx, name, type( arg )
		)
		error( msg, 3 )
	end
	return arg
end


local function lt( a, b )
	if a.interwiki ~= b.interwiki then
		return a.interwiki < b.interwiki
	end
	if a.namespace ~= b.namespace then
		return a.namespace < b.namespace
	end
	return a.text < b.text
end

local function getFileInfo( file )
	data = php.getFileInfo( file )
	return setmetatable( data, {
		__index = function( t, key )
			if key == 'metadata' then
				data.metadata = php.getFileMetadata( file )
				return data.metadata
			end
		end
	} )
end

local function makeTitleObject( data )
	if not data then
		return nil
	end

	local obj = {}
	local checkSelf = util.makeCheckSelfFunction( 'mw.title', 'title', obj, 'title object' );
	-- For external (interwiki) links, data.namespace is unknown and we'll
	-- end up just using the properties for the default namespace here.
	-- We'll fix it up below to avoid misleading the caller.
	local ns = mw.site.namespaces[data.namespace]

	local isCurrentTitle = data.isCurrentTitle
	data.isCurrentTitle = nil

	data.isContentPage = ns.isContent
	data.isExternal = data.interwiki ~= ''
	data.isSpecialPage = data.namespace == mw.site.namespaces.Special.id
	data.isTalkPage = ns.isTalk
	data.subjectNsText = ns.subject.name

	if ns.talk ~= nil then
		data.canTalk = true
		data.talkNsText = ns.talk.name
	else
		data.canTalk = false
	end

	if data.isExternal then
		-- For interwiki links we don't know the true value of any of these
		-- properties, so nil them out.
		data.isContentPage = nil
		data.isSpecialPage = nil
		data.isTalkPage = nil
		data.canTalk = nil
		data.subjectNsText = nil
		data.talkNsText = nil
	end

	data.prefixedText = data.text
	if data.nsText ~= '' then
		data.prefixedText = string.gsub( data.nsText .. ':' .. data.prefixedText, '_', ' ' )
	end
	if data.interwiki ~= '' then
		data.prefixedText = data.interwiki .. ':' .. data.prefixedText
	end

	local firstSlash, lastSlash
	if ns.hasSubpages then
		firstSlash, lastSlash = string.match( data.text, '^[^/]*().*()/[^/]*$' )
	end
	if firstSlash then
		data.isSubpage = true
		data.rootText = string.sub( data.text, 1, firstSlash - 1 )
		data.baseText = string.sub( data.text, 1, lastSlash - 1 )
		data.subpageText = string.sub( data.text, lastSlash + 1 )
	else
		data.isSubpage = false
		data.rootText = data.text
		data.baseText = data.text
		data.subpageText = data.text
	end

	function data:inNamespace( ns )
		checkSelf( self, 'inNamespace' )
		ns = checkNamespace( 'inNamespace', 1, ns )
		return ns == self.namespace
	end

	function data:inNamespaces( ... )
		checkSelf( self, 'inNamespaces' )
		for i = 1, select( '#', ... ) do
			local ns = checkNamespace( 'inNamespaces', i, select( i, ... ) )
			if ns == self.namespace then
				return true
			end
		end
		return false
	end

	function data:hasSubjectNamespace( ns )
		checkSelf( self, 'hasSubjectNamespace' )
		ns = checkNamespace( 'hasSubjectNamespace', 1, ns )
		return ns == mw.site.namespaces[self.namespace].subject.id
	end

	function data:isSubpageOf( title )
		checkSelf( self, 'isSubpageOf' )
		checkType( 'isSubpageOf', 1, title, 'table' )
		return self.interwiki == title.interwiki and
			self.namespace == title.namespace and
			title.text .. '/' == string.sub( self.text, 1, #title.text + 1 )
	end

	function data:subPageTitle( text )
		checkSelf( self, 'subpageTitle' )
		checkType( 'subpageTitle', 1, text, 'string' )
		return title.makeTitle( data.namespace, data.text .. '/' .. text )
	end

	function data:partialUrl()
		checkSelf( self, 'partialUrl' )
		return data.thePartialUrl
	end

	function data:fullUrl( query, proto )
		checkSelf( self, 'fullUrl' )
		return php.getUrl( self.fullText, 'fullUrl', query, proto )
	end

	function data:localUrl( query )
		checkSelf( self, 'localUrl' )
		return php.getUrl( self.fullText, 'localUrl', query )
	end

	function data:canonicalUrl( query )
		checkSelf( self, 'canonicalUrl' )
		return php.getUrl( self.fullText, 'canonicalUrl', query )
	end

	function data:getContent()
		-- deprecated: should use `title.content` instead
		checkSelf( self, 'getContent' )
		local content = php.getContent( self.fullText )
		data.getContent = function ( self )
			checkSelf( self, 'getContent' )
			return content
		end
		return content
	end

	-- Known fields, both those defined above and any dynamically handled in
	-- __index. Truthy values represent read-only, and falsey values represent
	-- read-write. If the value is the string 'e', expensive data will be loaded
	-- if the field is read.
	local readOnlyFields = {
		fragment = false,
		fullText = true,
		rootPageTitle = true,
		basePageTitle = true,
		talkPageTitle = true,
		subjectPageTitle = true,
		fileExists = true,
		file = true,
		protectionLevels = true,
		cascadingProtection = true,
		exists = 'e',
		isRedirect = 'e',
		contentModel = 'e',
		id = 'e',
		redirectTarget = true,
	}
	for k in pairs( data ) do
		readOnlyFields[k] = true
	end

	local function pairsfunc( t, k )
		local v
		repeat
			k = next( readOnlyFields, k )
			if k == nil then
				return nil
			end
			v = t[k]
		until v ~= nil
		return k, v
	end

	return setmetatable( obj, {
		__eq = title.equals,
		__lt = lt,
		__pairs = function ( t )
			return pairsfunc, t, nil
		end,
		__index = function ( t, k )
			if k == 'exists' and data.namespace == NS_MEDIA then
				k = 'fileExists'
			end

			if readOnlyFields[k] == 'e' and data[k] == nil then
				for k,v in pairs( php.getExpensiveData( t.fullText ) ) do
					data[k] = v
				end
			end

			if k == 'id' and isCurrentTitle then
				php.recordVaryFlag( t.fullText, 'vary-page-id' )
			end

			if k == 'fullText' then
				if data.fragment ~= '' then
					return data.prefixedText .. '#' .. data.fragment
				else
					return data.prefixedText
				end
			end

			if k == 'rootPageTitle' then
				return title.makeTitle( data.namespace, data.rootText )
			end
			if k == 'basePageTitle' then
				return title.makeTitle( data.namespace, data.baseText )
			end
			if k == 'talkPageTitle' then
				local ns = mw.site.namespaces[data.namespace].talk
				if not ns then
					return nil
				end
				if ns.id == data.namespace then
					return obj
				end
				return title.makeTitle( ns.id, data.text )
			end
			if k == 'subjectPageTitle' then
				local ns = mw.site.namespaces[data.namespace].subject
				if ns.id == data.namespace then
					return obj
				end
				return title.makeTitle( ns.id, data.text )
			end
			if k == 'pageLang' then
				if data.pageLang == nil then
					data.pageLang = mw.language.new(
						php.getPageLangCode( data.prefixedText )
					);
				end
				return data.pageLang
			end
			if k == 'file' then
				if data.file == nil then
					data.file = getFileInfo( data.prefixedText )
				end
				return data.file or nil
			end
			if k == 'fileExists' then -- Kept for backward compatibility. Since 1.25, file.exists is preferred over this
				return t.file and t.file.exists
			end
			if k == 'protectionLevels' then
				if data.protectionLevels == nil then
					data.protectionLevels = php.protectionLevels( data.prefixedText )
				end
				return data.protectionLevels
			end
			if k == 'cascadingProtection' then
				if data.cascadingProtection == nil then
					data.cascadingProtection = php.cascadingProtection( data.prefixedText )
				end
				return data.cascadingProtection
			end
			if k == 'redirectTarget' then
				if data.redirectTarget == nil then
					data.redirectTarget = makeTitleObject( php.redirectTarget( data.prefixedText ) ) or false
				end
				return data.redirectTarget
			end
			if k == 'content' then
				if data.content == nil then
					data.content = php.getContent( data.prefixedText ) or false
				end
				return data.content
			end
			if k == 'categories' then
				if data.categories == nil then
					data.categories = php.getCategories( data.prefixedText )
				end
				return data.categories
			end

			if data[k] == nil then
				data[k] = php.getAttributeValue( data.prefixedText, k )
			end

			return data[k]
		end,
		__newindex = function ( t, k, v )
			if k == 'fragment' then
				checkTypeForIndex( k, v, 'string' )
				v = string.gsub( v, '[%s_]+', ' ' )
				v = string.gsub( v, '^(.-) ?$', '%1' )
				data[k] = v
			elseif readOnlyFields[k] then
				error( "index '" .. k .. "' is read only", 2 )
			else
				readOnlyFields[k] = v and false  -- assigns nil if v == nil, false otherwise
				rawset( t, k, v )
			end
		end,
		__tostring = function ( t )
			return t.prefixedText
		end
	} )
end

function title.setupInterface( options )
	-- Boilerplate
	title.setupInterface = nil
	php = mw_interface
	mw_interface = nil
	NS_MEDIA = options.NS_MEDIA

	-- Set current title
	title.getCurrentTitle = function ()
		return makeTitleObject( mw.clone( options.thisTitle ) )
	end

	-- Register this library in the "mw" global
	mw = mw or {}
	mw.title = title

	package.loaded['mw.title'] = title
end

function title.newBatch( list, defaultNamespace )
	local batchObj = {}
	local titleList = list

	local lookupOptions = { existence = false }
	function batchObj:lookupExistence()
		lookupOptions.existence = true
		return batchObj
	end

	function batchObj:getTitles()
		local result = {}
		if lookupOptions.existence then
			local titleData = php.newBatchLookupExistence( list, defaultNamespace )
			for i, v in pairs( titleData ) do
				result[i] = makeTitleObject( v )
			end
		else
			for i, v in pairs( list ) do
				checkTypeForIndex( i, v, 'string' )
				result[i] = mw.title.new( v, defaultNamespace )
			end
		end
		return result
	end

	return batchObj
end

function title.new( text_or_id, defaultNamespace )
	return makeTitleObject( php.newTitle( text_or_id, defaultNamespace ) )
end

function title.makeTitle( ns, title, fragment, interwiki )
	return makeTitleObject( php.makeTitle( ns, title, fragment, interwiki ) )
end

function title.equals( a, b )
	return a.interwiki == b.interwiki and
		a.namespace == b.namespace and
		a.text == b.text
end

function title.compare( a, b )
	if a.interwiki ~= b.interwiki then
		return a.interwiki < b.interwiki and -1 or 1
	end
	if a.namespace ~= b.namespace then
		return a.namespace < b.namespace and -1 or 1
	end
	if a.text ~= b.text then
		return a.text < b.text and -1 or 1
	end
	return 0
end

return title
