{-# LANGUAGE TemplateHaskell #-}

-- Copyright (C) 2010-2012 John Millikin <john@john-millikin.com>
--
-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- any later version.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with this program.  If not, see <http://www.gnu.org/licenses/>.

module DBusTests.Introspection (test_Introspection) where

import           Test.Chell
import           Test.Chell.QuickCheck
import           Test.QuickCheck hiding (property)

import           Control.Applicative ((<$>), (<*>))
import           Control.Monad (liftM, liftM2)

import           DBus
import qualified DBus.Introspection as Introspection

import           DBusTests.InterfaceName ()
import           DBusTests.MemberName ()
import           DBusTests.ObjectPath ()
import           DBusTests.Signature ()
import           DBusTests.Util (halfSized)

test_Introspection :: Suite
test_Introspection = suite "Introspection"
	[ test_XmlPassthrough
	, test_XmlParse
	, test_XmlParseFailed
	, test_XmlWriteFailed
	]

test_XmlPassthrough :: Test
test_XmlPassthrough = property "xml-passthrough" $ \obj -> let
	path = Introspection.objectPath obj
	Just xml = Introspection.formatXML obj
	in Introspection.parseXML path xml == Just obj

test_XmlParse :: Test
test_XmlParse = assertions "xml-parse" $ do
	-- root object path can be inferred
	$expect (equal
		(Introspection.parseXML (objectPath_ "/") "<node><node name='foo'/></node>")
		(Just (Introspection.object (objectPath_ "/"))
			{ Introspection.objectChildren = 
				[ Introspection.object (objectPath_ "/foo")
				]
			}
		))

test_XmlParseFailed :: Test
test_XmlParseFailed = assertions "xml-parse-failed" $ do
	$expect (nothing (Introspection.parseXML (objectPath_ "/") "<invalid>"))
	$expect (nothing (Introspection.parseXML (objectPath_ "/") "<invalid/>"))
	
	-- invalid property access
	$expect (nothing (Introspection.parseXML (objectPath_ "/")
		"<node>\
		\  <interface name='com.example.Foo'>\
		\    <property type='s' access='invalid'>\
		\    </property>\
		\  </interface>\
		\</node>"))
	
	-- invalid parameter type
	$expect (nothing (Introspection.parseXML (objectPath_ "/")
		"<node>\
		\  <interface name='com.example.Foo'>\
		\    <method name='Foo'>\
		\      <arg type='yy'/>\
		\    </method>\
		\  </interface>\
		\</node>"))

test_XmlWriteFailed :: Test
test_XmlWriteFailed = assertions "xml-write-failed" $ do
	-- child's object path isn't under parent's
	$expect (nothing (Introspection.formatXML (Introspection.object (objectPath_ "/foo"))
		{ Introspection.objectChildren =
			[ Introspection.object (objectPath_ "/bar")
			]
		}))
	
	-- invalid type
	$expect (nothing (Introspection.formatXML (Introspection.object (objectPath_ "/foo"))
		{ Introspection.objectInterfaces =
			[ (Introspection.interface (interfaceName_ "/bar"))
				{ Introspection.interfaceProperties =
					[ Introspection.property "prop" (TypeDictionary TypeVariant TypeVariant)
					]
				}
			]
		}))

instance Arbitrary Type where
	arbitrary = oneof [atom, container] where
		atom = elements
			[ TypeBoolean
			, TypeWord8
			, TypeWord16
			, TypeWord32
			, TypeWord64
			, TypeInt16
			, TypeInt32
			, TypeInt64
			, TypeDouble
			, TypeString
			, TypeObjectPath
			, TypeSignature
			]
		container = oneof
			[ return TypeVariant
			, liftM TypeArray arbitrary
			, liftM2 TypeDictionary atom arbitrary
			, liftM TypeStructure (listOf1 (halfSized arbitrary))
			]

instance Arbitrary Introspection.Object where
	arbitrary = arbitrary >>= subObject

subObject :: ObjectPath -> Gen Introspection.Object
subObject parentPath = sized $ \n -> resize (min n 4) $ do
	let nonRoot = do
		x <- resize 10 arbitrary
		case formatObjectPath x of
			"/" -> nonRoot
			x'  -> return x'
	
	thisPath <- nonRoot
	let path' = case formatObjectPath parentPath of
		"/" -> thisPath
		x   -> x ++ thisPath
	let path = objectPath_ path'
	ifaces <- arbitrary
	children <- halfSized (listOf (subObject path))
	return (Introspection.object path)
		{ Introspection.objectInterfaces = ifaces
		, Introspection.objectChildren = children
		}

instance Arbitrary Introspection.Interface where
	arbitrary = do
		name <- arbitrary
		methods <- arbitrary
		signals <- arbitrary
		properties <- arbitrary
		return (Introspection.interface name)
			{ Introspection.interfaceMethods = methods
			, Introspection.interfaceSignals = signals
			, Introspection.interfaceProperties = properties
			}

instance Arbitrary Introspection.Method where
	arbitrary = do
		name <- arbitrary
		args <- arbitrary
		return (Introspection.method name)
			{ Introspection.methodArgs = args
			}

instance Arbitrary Introspection.Signal where
	arbitrary = do
		name <- arbitrary
		args <- arbitrary
		return (Introspection.signal name)
			{ Introspection.signalArgs = args
			}

instance Arbitrary Introspection.MethodArg where
	arbitrary = Introspection.methodArg
		<$> gen_Ascii
		<*> arbitrary
		<*> arbitrary

instance Arbitrary Introspection.Direction where
	arbitrary = elements [Introspection.directionIn, Introspection.directionOut]

instance Arbitrary Introspection.SignalArg where
	arbitrary = Introspection.signalArg
		<$> gen_Ascii
		<*> arbitrary

instance Arbitrary Introspection.Property where
	arbitrary = do
		name <- gen_Ascii
		t <- arbitrary
		canRead <- arbitrary
		canWrite <- arbitrary
		return (Introspection.property name t)
			{ Introspection.propertyRead = canRead
			, Introspection.propertyWrite = canWrite
			}

gen_Ascii :: Gen String
gen_Ascii = listOf (elements ['!'..'~'])
