define object pmltable
	-- данные колонок
	member .columnsname is array
	member .columnsdata	is array
	member .columnstype	is array
	-- сгруппированный массив индексов
	member .grupindices is array
	-- порядок для сортировки
	member .sortcolmids	is array
	member .sortcolmasc	is array
	-- разделитель для склейки ячеек
	member .mergedelimr	is array
	-- типы колонок
	member .ctgrp is string $* типа ячейки
	member .ctmer is string $* типа string
	member .ctsum is string $* типа real
	member .ctarr is string $* типа array
	-- статус таблицы
	member .issorted is boolean
endobject
--
define method .pmltable()
	-- данные колонок
	!this.columnsname = array()
	!this.columnsdata = array()
	!this.columnstype = array()
	-- сгруппированный массив индексов
	!this.grupindices = array()
	-- порядок для сортировки
	!this.sortcolmids = array()
	!this.sortcolmasc = array()
	-- разделитель для склейки ячеек
	!this.mergedelimr = array()
	-- типы колонок
	!this.ctgrp = 'grp'
	!this.ctmer = 'mer'
	!this.ctsum = 'sum'
	!this.ctarr = 'arr'
	-- статус таблицы
	!this.issorted = false
endmethod


--========================================================================================
-- добавить/удалить колонку
--========================================================================================
-- добавить колонку
define method .Add( !name is string , !data is array ) is any
	!this.add( !name.lowcase() , !data , || )
	return !this
endmethod
-- добавить колонку
define method .Add( !name is string , !data is array , !ctyp is string ) is any
	-- добавление системной колонки count
	if !this.columnsname.size() eq 0 and !name ne 'count' then
		!this.addCountColumn( !data.size() )
	endif
	-- проверка размерности колонки $!name
	if !this.columnsname.size() gt 0 and !data.size() ne !this.getRowsCount() then
		$P ошибка размерности addcolumn $!name
		return !this
	endif
	-- проверка дублей имен колонки $!name
	if !this.columnsname.findfirst( !name ).set() then
		$P ошибка колонка с именем $!name уже существует
		return !this
	endif
	-- добавление колоноки $!name
	!this.columnsname.append( !name )
	!this.columnsdata.append( !data )
	!this.columnstype.append( !ctyp )
	--
	return !this
endmethod
-- удалить колонку
define method .Remove( !name is string )
	!cnum = !this.columnsname( !name )
	if !cnum.set() then
		!this.columnsname[!cnum].delete()
		!this.columnsdata[!cnum].delete()
		!this.columnstype[!cnum].delete()
		!this.columnsname.compress()
		!this.columnsdata.compress()
		!this.columnstype.compress()
	else
		$P ошибка колонка с именем $!name не существует
	endif
endmethod
--========================================================================================

--========================================================================================
--задать типы колонок (для группировки)
--========================================================================================
define method .CTypeIsGrup( !names is array )
	do !name values !names
		!this.CTypeIsGrup( !name )
	enddo
endmethod
-- тип колонки "для группировки" при объединении значения ячеек будут уникальны
define method .CTypeIsGrup( !name is string )
	!cnum = !this.columnsname.findfirst( !name )
	!this.columnstype[ !cnum ] = !this.ctgrp
endmethod
--
define method .CTypeIsSumm( !names is array )
	do !name values !names
		!this.CTypeIsSumm( !name )
	enddo
endmethod
-- тип колонки "сумма" при объединении ячеек в группы суммировать значения ячеек
define method .CTypeIsSumm( !name is string )
	!cnum = !this.columnsname.findfirst( !name )
	!this.columnstype[ !cnum ] = !this.ctsum
endmethod
--
define method .CTypeIsArry( !names is array )
	do !name values !names
		!this.CTypeIsArry( !name )
	enddo
endmethod
-- тип колонки "массив" при объединении ячеек в группы собирать значения ячеек в массив
define method .CTypeIsArry( !name is string )
	!cnum = !this.columnsname.findfirst( !name )
	!this.columnstype[ !cnum ] = !this.ctarr
endmethod
--
define method .CTypeIsMerg( !names is array , !delimer is string )
	do !name values !names
		!this.CTypeIsMerg( !name, !delimer )
	enddo
endmethod
-- тип колонки "слияние" при объединении ячеек в группы склеивать ячейки через разделитель
define method .CTypeIsMerg( !name is string , !delimer is string )
	!cnum = !this.columnsname.findfirst( !name )
	!this.mergedelimr[ !cnum ] = !delimer
	!this.columnstype[ !cnum ] = !this.ctmer
endmethod
--========================================================================================

--========================================================================================
--задать порядок сортировки
--========================================================================================
-- колонки по прорядку сортировки (в порядке понижения приоритета)
define method .SortBy( !names is array )
	do !name values !names
		!this.SortBy( !name )
	enddo
endmethod
--
define method .SortBy( !name is string )
	!this.SortBy( !name , true )
endmethod
--
define method .SortBy( !name is string , !ascending is boolean )
	!cnum = !this.columnsname.findfirst( !name )
	if !cnum.unset() then
		return error 1 'PMLTABLE.SortBy error:  Колонки с таким именем не существует. (!name)'
	else
		!this.sortcolmids.append( !cnum )
		!this.sortcolmasc.append( !ascending )
	endif
	-- статус таблицы
	!this.issorted = false
endmethod
--========================================================================================

-- выполнение сортировки и группировки
define method .Run()
	--=======================================================================
	-- нет колонок для сортировки
	if !this.columnsdata.size() eq 0 then
		return
	endif
	--=======================================================================
	-- сортировка таблицы ( результат в .columnsname .columnsdata .columnstype )
	!this.sorttable()
	-- группировка таблицы ( результат в .grupindices )
	!this.grouptable()
	--=======================================================================
endmethod
-- сброс типов колонок
define method .Reset()
	-- порядок для сортировки
	!this.sortcolmids = array()
	!this.sortcolmasc = array()
	-- сгруппированный массив индексов
	!this.grupindices = array()
	-- типы колонок
	do !i indices !this.columnstype
		if !this.columnsname[!i] ne 'count' then
			!this.columnstype[!i] = ||
		endif
	enddo
	-- статус таблицы
	!this.issorted = false
endmethod
-- запрос таблицы по заданным колонкам
define method .GetTable( !columnsame is array ) is array
	!columns = array()
	do !cname values !columnsame
		!columns.append(!this.getColumn(!cname))
	enddo
	return !this.columnsToTable( !columns )
endmethod
-- запрос колонки
define method .GetColumn( !name is string ) is array
	-- сгруппированный массив индексов
	if !this.grupindices.size() eq 0 then
		!this.Run()
	endif
	-- системная колонка rownumbers
	if !name.lowcase() eq 'row numbers' then
		return !this.GetColumn( 'count' ).indices()
	endif
	-- количество
	!ckey = !this.columnsname.findfirst( !name.lowcase() )
	--
	if !ckey.unset() then
		$P Колонка с именем $!name не задана
		return array()
	endif
	!guids = !this.grupindices
	-- подготовка массива строк
	!rcount = !guids.size()
	-- заполнение данных
	!cname = !this.columnsname[!ckey] $* имя колонки
	!ctype = !this.columnstype[!ckey] $* тип колонки
	--!cdata = !this.columnsdata[!ckey] $* данные колонки
	--
	!result	= array()
	do !rnum from 1 to !rcount	
		-- !guids[!rnum] => !inds индексы группы
		--if ((!guids[!rnum].size() eq 1) or (!ctype eq !this.ctgrp)) and (!ctype ne !this.ctarr) and (!ctype ne !this.ctsum) and (!ctype ne !this.ctmer) then

		if (!guids[!rnum].size() eq 1) and (!ctype eq ||) then
			-- группа из одной строки
			!result[!rnum] = !this.columnsdata[!ckey][ !guids[!rnum][1] ]
		else
			-- различные способы объединения ячеек	
			if !ctype eq !this.ctgrp then
				-- ячейка типа группы (уникальна)
				!result[!rnum] = !this.columnsdata[!ckey][ !guids[!rnum][1] ]		
			elseif !ctype eq !this.ctsum then
				-- сумма ячеек
				!result[!rnum] = !this.arraytoreal( !this.gdata(!this.columnsdata[!ckey], !guids[!rnum]) )
			elseif !ctype eq !this.ctmer then
				-- объединение ячеек
				!result[!rnum] = !this.arraytostring( !this.gdata(!this.columnsdata[!ckey], !guids[!rnum]), !this.mergedelimr[!ckey] )
			elseif !ctype eq !this.ctarr then
				-- массив
				!result[!rnum] = !this.gdata(!this.columnsdata[!ckey], !guids[!rnum])			
			else		
				q var !ctype
				$P Не задан тип колонки $!cname $!ctype
				return array()
			endif
		endif
	enddo
	return !result
endmethod


-- сортировка исходной таблицы
define method .sorttable()
	!this.grupindices = array()
	!sortindices = !this.getSortedIndices()
	do !cnum indices !this.columnsdata
		!this.columnsdata[ !cnum ].reindex( !sortindices )
	enddo
	-- статус таблицы
	!this.issorted = true
endmethod
-- группировка таблицы (определение .grupindices)
define method .grouptable()
	!this.grupindices = array()
	!gcnums = !this.columnstype.find( !this.ctgrp )
	!this.grupindices = !this.groupindices( !gcnums )
endmethod


-- добавление системной колонки count
define method .addCountColumn( !rcount is real )
	!data = array()
	do !i from 1 to !rcount
		!data.append( 1 )
	enddo
	!this.Add( 'count' , !data , !this.ctsum )
endmethod
-- массив колонок в массив строк (таблица)
define method .columnsToTable( !columns is array ) is array
	!dtext	= array()
	!rcount	= !columns.first().size()
	do !cnum from 1 to !columns.size()
		do !rnum from 1 to !rcount
			!dtext[!rnum][!cnum] = !columns[!cnum][!rnum]
		enddo		
	enddo
	return !dtext
endmethod
-- количество строк
define method .getRowsCount() is real
	if !this.columnsdata.size() eq 0 then
		return 0
	else
		return !this.columnsdata.first().size()
	endif
endmethod
-- массив индексов для сортировки
define method .getSortedIndices() is array
	!cnums = !this.sortcolmids
	!ascen = !this.sortcolmasc
	return !this.sortedindices( !cnums, !ascen )
endmethod
-- определяет, требуется ли дополнительная сортировка перед группировкой
define method .needGroupSort() is boolean
	$(
		Для корректной группировки, требуется, чтобы таблица была отсортирована по группирововчным столбцам
		Пример:
		
		сортировка	идет по столбцам 1 7 5 4 (порядок важен!)
		группировка	идет по столбцам 5 7
		1 [7 5] 4 => содержит подряд [5 7] => доп сортировка не требуется!
			
		сортировка	идет по столбцам 1 7 4 5 (порядок важен!)
		группировка	идет по столбцам 5 7
		1 [7] 4 [5] => не содержит подряд [5 7] => доп сортировка требуется!
	$)
	-- * не сортировать дополнительно для группировки (не работает) *
	return false
	-- таблица не отсортирована
	if !this.issorted.not() then
		return true
	endif
	-- массив колонок для сортировки
	!scnums = !this.sortcolmids
	-- массив колонок для группировки
	!gcnums = !this.columnstype.find( !this.ctgrp )
	-- если в массиве колонок для сортировки нет всех колонок для группировки
	q var !scnums !gcnums
	do !cnum values !gcnums
		if !scnums.findfirst(!cnum).unset() then
			-- требует сортировки для группировки
			return true
		endif
	enddo
	-- флаг начала цепочки
	!strt = false
	do !i values !scnums
		!gcnumid = !gcnums.findfirst(!i)
		if !gcnumid.set() then
			-- старт цепочки
			if !strt.not() then
				!strt = true
			endif
			-- колонка для группировки учтена
			!gcnums.remove( !gcnumid )
			!gcnums.compress()
			-- все колонки для группировки учтены
			if !gcnums.size() eq 0 then
				!strt = false
				break
			endif
		else
			if !strt then
				-- цепочка разорвана, требуется сортировка для группировки
				return true
			endif
		endif
	enddo
	-- не трубется сортировка для группировки
	return false
endmethod
-- вспомогательная сортировка для группировки
define method .getGroupSortIndices( !gcnums is array ) is array
	-- требует дополнительной сортировки перед группировкой
	!needGroupSort = !this.needGroupSort()
	if !needGroupSort then
		return !this.sortedindices( !gcnums )
	else
		return !this.columnsdata.first().indices()
	endif
endmethod


-- сортировка
define method .sortedindices( !cnums is array ) is array
	!ascen = array()
	do !i from 1 to !cnums.size()
		!ascen.append( true )
	enddo
	return !this.sortedindices( !cnums, !ascen )
endmethod
-- сортировка 
-- !cnums массив индексов колонок 
-- !ascen массив флага направления для сортировки
define method .sortedindices( !cnums is array , !ascen is array ) is array
	if !cnums.size() eq 0 then
		return array()
	endif
	-- сортировочный массив	
	!sort = array()
	do !cnum values !cnums
		!sort.append( !this.columnsdata[ !cnum ] )
	enddo
	!sort = !sort.invert()	
	-- 
	!indices = !sort.first().indices()
	do !i from 1 to !sort.size()
		-- сортировка
		!index = !sort[!i].sortedindices()
		if !ascen[!i].not() then
			!index = !index.invert()
		endif
		--
		do !j from !i + 1 to !sort.size()
			!sort[!j].reindex( !index )
		enddo
		!indices.reindex( !index )
	enddo
	--
	return !indices
endmethod
-- группировка
-- !cnums массив индексов колонок для группировки	
define method .groupindices( !cnums is array ) is array
	!grupindices = array()
	-- отсортированный массив индексов для группировки
	!groupsortindices = !this.getGroupSortIndices( !cnums )
	-- группировка
	-- !aftersort = array()
	!prid = -1 $* id предыдущей строки
	do !i from 1 to !groupsortindices.size()
		!crid = !groupsortindices[!i] $* id текущей строки
		if ( !cnums.size() eq 0 ) or ( !prid eq -1 )  then
			-- нет полей гля группировки или первая строка
			!isnew = true
		else
			-- выборка ID колонок для сортировки и группировки
			!isnew = false
			do !cnum values !cnums
				if !this.columnsdata[!cnum][!crid] ne !this.columnsdata[!cnum][!prid] then
					!isnew = true
					break
				endif
			enddo
		endif
		!prid = !crid
		--
		if !isnew then
			!tarr = array()
			!tarr[1] = !crid
			!grupindices.append( !tarr )
			-- !aftersort.append( !crid )
		else
			!grupindices[ !grupindices.size() ].append( !crid )
		endif
	enddo
	-- обратная сортировка
	-- !grupindices.reindex( !aftersort.sortedindices() )
	return !grupindices
endmethod

--========================================================================================
-- вспомогательные методы для объединения ячеек при группировке
--========================================================================================
-- возврат суммы поля
define method .arrayToReal( !array is array ) is real
	!sum = 0
	do !data values !array
		!sum = !sum + !data.real()
	enddo
	return !sum
endmethod
-- возврат объединенного поля
define method .arrayToString( !array is array , !delimer is string ) is string	
	!result = ||
	do !i indices !array
		if !i eq 1 then
			!result = !array[!i].string()
		else
			!result = !result & !delimer & !array[!i]
		endif
	enddo
	return !result
endmethod
-- выборка подмассива из массива по индексам
-- !cdata исходный массив
-- !indices массив индексов для выборки
define method .gdata( !cdata is array , !indices is array ) is array
	!result = array()
	do !id values !indices
		if !cdata[!id].set() then
			!result.append( !cdata[!id] )
		endif
	enddo
	return !result
endmethod
