# -*- coding: UTF-8 -*-
#NVDAObjects/IAccessible/sysListView32.py
#A part of NonVisual Desktop Access (NVDA)
#Copyright (C) 2006-2012 NV Access Limited, Peter Vágner
#This file is covered by the GNU General Public License.
#See the file COPYING for more details.

import time
from ctypes import *
from ctypes.wintypes import *
from comtypes import BSTR
import oleacc
import NVDAHelper
import watchdog
import controlTypes
import speech
import api
import eventHandler
import winKernel
import winUser
from . import IAccessible, List
from ..window import Window
import watchdog
from NVDAObjects.behaviors import RowWithoutCellObjects, RowWithFakeNavigation
import config

#Window messages
LVM_FIRST=0x1000
LVM_GETITEMSTATE=LVM_FIRST+44
LVM_GETFOCUSEDGROUP=LVM_FIRST+93
LVM_GETITEMCOUNT=LVM_FIRST+4
LVM_GETITEM=LVM_FIRST+75
LVN_GETDISPINFO=0xFFFFFF4F
LVM_GETITEMTEXTW=LVM_FIRST+115
LVM_GETHEADER=LVM_FIRST+31
LVM_GETCOLUMNORDERARRAY=LVM_FIRST+59
LVM_GETCOLUMNW=LVM_FIRST+95
LVM_GETSELECTEDCOUNT =(LVM_FIRST+50)
LVNI_SELECTED =2
LVM_GETNEXTITEM =(LVM_FIRST+12)
LVM_GETVIEW=LVM_FIRST+143
LV_VIEW_DETAILS=0x0001
LVM_GETSUBITEMRECT=LVM_FIRST+56
LV_VIEW_TILE=0x0004

#item mask flags
LVIF_TEXT=0x01 
LVIF_IMAGE=0x02
LVIF_PARAM=0x04
LVIF_STATE=0x08
LVIF_INDENT=0x10
LVIF_GROUPID=0x100
LVIF_COLUMNS=0x200

#Item states
LVIS_FOCUSED=0x01
LVIS_SELECTED=0x02
LVIS_STATEIMAGEMASK=0xF000

LVS_REPORT=0x0001
LVS_TYPEMASK=0x0003
LVS_OWNERDRAWFIXED=0x0400

#column mask flags
LVCF_FMT=1
LVCF_WIDTH=2
LVCF_TEXT=4
LVCF_SUBITEM=8
LVCF_IMAGE=16
LVCF_ORDER=32

CBEMAXSTRLEN=260

# listview header window messages
HDM_FIRST=0x1200
HDM_GETITEMCOUNT=HDM_FIRST

class LVITEM(Structure):
	_fields_=[
		('mask',c_uint),
		('iItem',c_int),
		('iSubItem',c_int),
		('state',c_uint),
		('stateMask',c_uint),
		('pszText',c_void_p),
		('cchTextMax',c_int),
		('iImage',c_int),
		('lParam',LPARAM),
		('iIndent',c_int),
		('iGroupID',c_int),
		('cColumns',c_uint),
		('puColumns',c_uint),
		('piColFmt',POINTER(c_int)),
		('iGroup',c_int),
	]

class LVITEM64(Structure):
	_fields_=[
		('mask',c_uint),
		('iItem',c_int),
		('iSubItem',c_int),
		('state',c_uint),
		('stateMask',c_uint),
		('pszText',c_ulonglong),
		('cchTextMax',c_int),
		('iImage',c_int),
		('lParam',c_ulonglong),
		('iIndent',c_int),
		('iGroupID',c_int),
		('cColumns',c_uint),
		('puColumns',c_uint),
		('piColFmt',c_ulonglong),
		('iGroup',c_int),
	]

class LVCOLUMN(Structure):
	_fields_=[
		('mask',c_uint),
		('fmt',c_int),
		('cx',c_int),
		('pszText',c_void_p),
		('cchTextMax',c_int),
		('iSubItem',c_int),
		('iImage',c_int),
		('iOrder',c_int),
		('cxMin',c_int),
		('cxDefault',c_int),
		('cxIdeal',c_int),
	]

class LVCOLUMN64(Structure):
	_fields_=[
		('mask',c_uint),
		('fmt',c_int),
		('cx',c_int),
		('pszText',c_ulonglong),
		('cchTextMax',c_int),
		('iSubItem',c_int),
		('iImage',c_int),
		('iOrder',c_int),
		('cxMin',c_int),
		('cxDefault',c_int),
		('cxIdeal',c_int),
	]

class AutoFreeBSTR(BSTR):
	"""A BSTR that *always* frees itself on deletion.""" 
	_needsfree=True

class List(List):

	def getListGroupInfo(self,groupIndex):
		header=AutoFreeBSTR()
		footer=AutoFreeBSTR()
		state=c_int()
		if watchdog.cancellableExecute(NVDAHelper.localLib.nvdaInProcUtils_sysListView32_getGroupInfo,self.appModule.helperLocalBindingHandle,self.windowHandle,groupIndex,byref(header),byref(footer),byref(state))!=0:
			return None
		return dict(header=header.value,footer=footer.value,state=state.value,groupIndex=groupIndex)

	def _get_name(self):
		name=super(List,self)._get_name()
		if not name:
			name=super(IAccessible,self)._get_name()
		return name

	def event_gainFocus(self):
		#See if this object is the focus and the focus is on a group item.
		#if so, then morph this object to a groupingItem object
		if self is api.getFocusObject():
			groupIndex=watchdog.cancellableSendMessage(self.windowHandle,LVM_GETFOCUSEDGROUP,0,0)
			if groupIndex>=0:
				info=self.getListGroupInfo(groupIndex)
				if info is not None:
					ancestors=api.getFocusAncestors()
					if api.getFocusDifferenceLevel()==len(ancestors)-1:
						self.event_focusEntered()
					groupingObj=GroupingItem(windowHandle=self.windowHandle,parentNVDAObject=self,groupInfo=info)
					return eventHandler.queueEvent("gainFocus",groupingObj)
		return super(List,self).event_gainFocus()

	def _get_isMultiColumn(self):
		view =  watchdog.cancellableSendMessage(self.windowHandle, LVM_GETVIEW, 0, 0)
		if view in (LV_VIEW_DETAILS, LV_VIEW_TILE):
			return True
		elif view == 0:
			# #2673: This could indicate that LVM_GETVIEW is not supported (comctl32 < 6.0).
			# Unfortunately, it could also indicate LV_VIEW_ICON.
			# Hopefully, no one sets LVS_REPORT and then LV_VIEW_ICON.
			return self.windowStyle & LVS_TYPEMASK == LVS_REPORT
		return False

	def _get_rowCount(self):
		return watchdog.cancellableSendMessage(self.windowHandle, LVM_GETITEMCOUNT, 0, 0)

	def _get_columnCount(self):
		if not self.isMultiColumn:
			return 0
		headerHwnd= watchdog.cancellableSendMessage(self.windowHandle,LVM_GETHEADER,0,0)
		count = watchdog.cancellableSendMessage(headerHwnd, HDM_GETITEMCOUNT, 0, 0)
		if not count:
			return 1
		return count

	def _get__columnOrderArray(self):
		coa=(c_int *self.columnCount)()
		processHandle=self.processHandle
		internalCoa=winKernel.virtualAllocEx(processHandle,None,sizeof(coa),winKernel.MEM_COMMIT,winKernel.PAGE_READWRITE)
		try:
			winKernel.writeProcessMemory(processHandle,internalCoa,byref(coa),sizeof(coa),None)
			res = watchdog.cancellableSendMessage(self.windowHandle,LVM_GETCOLUMNORDERARRAY, self.columnCount, internalCoa)
			if res:
				winKernel.readProcessMemory(processHandle,internalCoa,byref(coa),sizeof(coa),None)
		finally:
			winKernel.virtualFreeEx(processHandle,internalCoa,0,winKernel.MEM_RELEASE)
		return coa

class GroupingItem(Window):

	def __init__(self,windowHandle=None,parentNVDAObject=None,groupInfo=None):
		super(GroupingItem,self).__init__(windowHandle=windowHandle)
		self.parent=parentNVDAObject
		self.groupInfo=groupInfo

	def _isEqual(self,other):
		return isinstance(other,self.__class__) and self.groupInfo==other.groupInfo

	def _set_groupInfo(self,info):
		self._groupInfoTime=time.time()
		self._groupInfo=info
		for gesture in ("kb:leftArrow", "kb:rightArrow"):
			self.bindGesture(gesture, "collapseOrExpand")

	def _get_groupInfo(self):
		now=time.time()
		if (now-self._groupInfoTime)>0.25:
			self._groupInfoTime=now
			self._groupInfo=self.parent.getListGroupInfo(self._groupInfo['groupIndex'])
		return self._groupInfo

	def _get_name(self):
		return self.groupInfo['header']

	role = controlTypes.ROLE_GROUPING

	def _get_value(self):
		return self.groupInfo['footer']

	def _get_states(self):
		states=set()
		if self.groupInfo['state']&1:
			states.add(controlTypes.STATE_COLLAPSED)
		else:
			states.add(controlTypes.STATE_EXPANDED)
		return states

	def script_collapseOrExpand(self,gesture):
		gesture.send()
		eventHandler.queueEvent("stateChange",self)

class ListItemWithoutColumnSupport(IAccessible):

	def initOverlayClass(self):
		if self.appModule.is64BitProcess:
			self.LVITEM = LVITEM64
			self.LVCOLUMN = LVCOLUMN64
		else:
			self.LVITEM = LVITEM
			self.LVCOLUMN = LVCOLUMN

	description = None

	def _get_value(self):
		value=super(ListItemWithoutColumnSupport,self)._get_description()
		if (not value or value.isspace()) and self.windowStyle & LVS_OWNERDRAWFIXED:
			value=self.displayText
		if not value:
			return None
		#Some list view items in Vista can contain annoying left-to-right and right-to-left indicator characters which really should not be there.
		value=value.replace(u'\u200E','')
		value=value.replace(u'\u200F','')
		return value

	def _get_positionInfo(self):
		index=self.IAccessibleChildID
		totalCount=watchdog.cancellableSendMessage(self.windowHandle,LVM_GETITEMCOUNT,0,0)
		return dict(indexInGroup=index,similarItemsInGroup=totalCount) 

	def event_stateChange(self):
		if self.hasFocus:
			super(ListItemWithoutColumnSupport,self).event_stateChange()

class ListItem(RowWithFakeNavigation, RowWithoutCellObjects, ListItemWithoutColumnSupport):

	def _getColumnLocationRaw(self,index):
		processHandle=self.processHandle
		localRect=RECT(left=2,top=index)
		internalRect=winKernel.virtualAllocEx(processHandle,None,sizeof(localRect),winKernel.MEM_COMMIT,winKernel.PAGE_READWRITE)
		try:
			winKernel.writeProcessMemory(processHandle,internalRect,byref(localRect),sizeof(localRect),None)
			watchdog.cancellableSendMessage(self.windowHandle,LVM_GETSUBITEMRECT, (self.IAccessibleChildID-1), internalRect)
			winKernel.readProcessMemory(processHandle,internalRect,byref(localRect),sizeof(localRect),None)
		finally:
			winKernel.virtualFreeEx(processHandle,internalRect,0,winKernel.MEM_RELEASE)
		windll.user32.ClientToScreen(self.windowHandle,byref(localRect))
		windll.user32.ClientToScreen(self.windowHandle,byref(localRect,8))
		return (localRect.left,localRect.top,localRect.right-localRect.left,localRect.bottom-localRect.top)

	def _getColumnLocation(self,column):
		return self._getColumnLocationRaw(self.parent._columnOrderArray[column - 1])

	def _getColumnContentRaw(self, index):
		buffer=None
		processHandle=self.processHandle
		internalItem=winKernel.virtualAllocEx(processHandle,None,sizeof(self.LVITEM),winKernel.MEM_COMMIT,winKernel.PAGE_READWRITE)
		try:
			internalText=winKernel.virtualAllocEx(processHandle,None,CBEMAXSTRLEN*2,winKernel.MEM_COMMIT,winKernel.PAGE_READWRITE)
			try:
				item=self.LVITEM(iItem=self.IAccessibleChildID-1,mask=LVIF_TEXT|LVIF_COLUMNS,iSubItem=index,pszText=internalText,cchTextMax=CBEMAXSTRLEN)
				winKernel.writeProcessMemory(processHandle,internalItem,byref(item),sizeof(self.LVITEM),None)
				len = watchdog.cancellableSendMessage(self.windowHandle,LVM_GETITEMTEXTW, (self.IAccessibleChildID-1), internalItem)
				if len:
					winKernel.readProcessMemory(processHandle,internalItem,byref(item),sizeof(self.LVITEM),None)
					buffer=create_unicode_buffer(len)
					winKernel.readProcessMemory(processHandle,item.pszText,buffer,sizeof(buffer),None)
			finally:
				winKernel.virtualFreeEx(processHandle,internalText,0,winKernel.MEM_RELEASE)
		finally:
			winKernel.virtualFreeEx(processHandle,internalItem,0,winKernel.MEM_RELEASE)
		return buffer.value if buffer else None

	def _getColumnContent(self, column):
		return self._getColumnContentRaw(self.parent._columnOrderArray[column - 1])

	def _getColumnHeaderRaw(self,index):
		buffer=None
		processHandle=self.processHandle
		internalColumn=winKernel.virtualAllocEx(processHandle,None,sizeof(self.LVCOLUMN),winKernel.MEM_COMMIT,winKernel.PAGE_READWRITE)
		try:
			internalText=winKernel.virtualAllocEx(processHandle,None,CBEMAXSTRLEN*2,winKernel.MEM_COMMIT,winKernel.PAGE_READWRITE)
			try:
				column=self.LVCOLUMN(mask=LVCF_TEXT,iSubItem=index,pszText=internalText,cchTextMax=CBEMAXSTRLEN)
				winKernel.writeProcessMemory(processHandle,internalColumn,byref(column),sizeof(self.LVCOLUMN),None)
				res = watchdog.cancellableSendMessage(self.windowHandle,LVM_GETCOLUMNW, index, internalColumn)
				if res:
					winKernel.readProcessMemory(processHandle,internalColumn,byref(column),sizeof(self.LVCOLUMN),None)
					buffer=create_unicode_buffer(column.cchTextMax)
					winKernel.readProcessMemory(processHandle,column.pszText,buffer,sizeof(buffer),None)
			finally:
				winKernel.virtualFreeEx(processHandle,internalText,0,winKernel.MEM_RELEASE)
		finally:
			winKernel.virtualFreeEx(processHandle,internalColumn,0,winKernel.MEM_RELEASE)
		return buffer.value if buffer else None

	def _getColumnHeader(self, column):
		return self._getColumnHeaderRaw(self.parent._columnOrderArray[column - 1])

	def _get_name(self):
		parent = self.parent
		if not isinstance(parent, List) or not parent.isMultiColumn or self._shouldDisableMultiColumn:
			name = super(ListItem, self).name
			if name:
				return name
			elif self.windowStyle & LVS_OWNERDRAWFIXED:
				return self.displayText
			return name
		textList = []
		for col in xrange(1, self.childCount + 1):
			content = self._getColumnContent(col)
			if not content:
				continue
			if config.conf["documentFormatting"]["reportTableHeaders"] and col != 1:
				header = self._getColumnHeader(col)
			else:
				header = None
			if header:
				textList.append("%s: %s" % (header, content))
			else:
				textList.append(content)
		return "; ".join(textList)

	value = None

	def _get__shouldDisableMultiColumn(self):
		if self.windowStyle & LVS_OWNERDRAWFIXED:
			# This is owner drawn, but there may still be column content.
			# accDescription will be empty if there is no column content,
			# in which case multi-column support must be disabled.
			ret = not super(ListItemWithoutColumnSupport, self).description
			if ret:
				self.childCount = 0
		else:
			ret = False
		self._shouldDisableMultiColumn = ret
		return ret
