<?php
/*
Есть продукты A, B, C, D, E, F, G, H, I, J, K, L, M. Каждый продукт стоит определенную сумму.
Есть набор правил расчета итоговой суммы:
Если одновременно выбраны А и B, то их суммарная стоимость уменьшается на 10% (для каждой пары А и B)
Если одновременно выбраны D и E, то их суммарная стоимость уменьшается на 5% (для каждой пары D и E)
Если одновременно выбраны E,F,G, то их суммарная стоимость уменьшается на 5% (для каждой тройки E,F,G)
Если одновременно выбраны А и один из [K,L,M], то стоимость выбранного продукта уменьшается на 5%
Если пользователь выбрал одновременно 3 продукта, он получает скидку 5% от суммы заказа
Если пользователь выбрал одновременно 4 продукта, он получает скидку 10% от суммы заказа
Если пользователь выбрал одновременно 5 продуктов, он получает скидку 20% от суммы заказа
Описанные скидки 5,6,7 не суммируются, применяется только одна из них
Продукты A и C не участвуют в скидках 5,6,7
Каждый товар может участвовать только в одной скидке. Скидки применяются последовательно в порядке описанном выше.
Необходимо написать программу на PHP с использованием ООП которая имея на входе набор продуктов (один продукт может встречаться несколько раз) рассчитывала суммарную их стоимость.
*/
class ProductCollection
{
private $products ;
public function __construct
( array $products = [ ] ) {
foreach ( $products as $product ) {
$this -> addProduct ( $product ) ;
}
}
public function addProduct( Product $product )
{
$this -> products [ ] = $product ;
}
public function getByName( $name )
{
foreach ( $this -> products as $product ) {
if ( $product -> name === $name ) {
return $product ;
}
}
return null ;
}
public function getCount( )
{
return count ( $this -> products ) ; }
public function getWithoutNames
( array $withoutNames ) {
$this -> products ,
function ( Product $p ) use ( $withoutNames ) {
return ! in_array ( $p -> name , $withoutNames ) ; }
) ;
return new self ( $productsWithoutNames ) ;
}
public function getSum( )
{
$this -> products ,
function ( $prev , $next ) { return $prev + $next -> price ; } ,
0
) ;
}
public function hasNames
( array $names ) {
$productsNames = array_map ( function ( $p ) { return $p -> name ; } , $this -> products ) ; }
public function getOneOfName
( array $names ) {
foreach ( $this -> products as $product ) {
return $product ;
}
}
return null ;
}
public function deleteByNames
( array $names ) {
$deleted = [ ] ;
foreach ( $names as $name ) {
$deleted [ ] = $this -> deleteByName ( $name ) ;
}
return new self ( $deleted ) ;
}
public function deleteByName( $name )
{
$productCount = count ( $this -> products ) ; for ( $i = 0 ; $i < $productCount ; $i ++ ) {
if ( $this -> products [ $i ] -> name === $name ) {
}
}
return null ;
}
}
class Product
{
public $name ;
public $price ;
public function __construct( $name , $price )
{
$this -> name = $name ;
$this -> price = $price ;
}
}
// Скидка, формирующаяся на основе количества элементов в списке товаров
class CountDiscount
{
private $criteria ;
private $percent ;
private $withoutNames ;
public function __construct
( CountCriteria
$criteria , $percent , array $withoutNames = [ ] ) {
$this -> criteria = $criteria ;
$this -> percent = $percent ;
$this -> withoutNames = $withoutNames ;
}
public function match( ProductCollection $pc )
{
$countWithoutNames = $pc -> getWithoutNames ( $this -> withoutNames ) -> getCount ( ) ;
return $this -> criteria -> check ( $countWithoutNames ) ;
}
public function getPercent( )
{
return $this -> percent ;
}
}
interface CombinationDiscountInterface
{
public function getSumAfterApplyingDiscount( ProductCollection $productCollection ) ;
}
// Скидка, формирующаяся на основе комбинации определённых товаров
class CombinationDiscount implements CombinationDiscountInterface
{
private $productNames ;
private $percent ;
public function __construct
( array $productNames , $percent ) {
$this -> productNames = $productNames ;
$this -> percent = $percent ;
}
public function getSumAfterApplyingDiscount( ProductCollection $pc )
{
if ( $pc -> hasNames ( $this -> productNames ) ) {
$deleted = $pc -> deleteByNames ( $this -> productNames ) ;
return $deleted -> getSum ( ) - $deleted -> getSum ( ) * $this -> percent ;
}
return 0 ;
}
}
// Если одновременно выбраны А и один из [K,L,M], то стоимость выбранного продукта...
class CombinationOneOfDiscount implements CombinationDiscountInterface
{
private $productName ;
private $oneOf ;
private $percent ;
public function __construct
( $productName , array $oneOf , $percent ) {
$this -> productName = $productName ;
$this -> oneOf = $oneOf ;
$this -> percent = $percent ;
}
public function getSumAfterApplyingDiscount( ProductCollection $pc )
{
$oneOf = $pc -> getOneOfName ( $this -> oneOf ) ;
$product = $pc -> getByName ( $this -> productName ) ;
if ( $oneOf && $pc -> hasNames ( [ $this -> productName ] ) ) {
$pc -> deleteByNames ( [ $oneOf -> name , $product -> name ] ) ;
return $product -> price + ( $oneOf -> price - $oneOf -> price * $this -> percent ) ;
}
return 0 ;
}
}
class Calculator
{
private $combinationDiscounts ;
private $productCollection ;
private $countDiscounts ;
public function __construct( ProductCollection $productCollection )
{
$this -> productCollection = $productCollection ;
$this -> combinationDiscounts = [ ] ;
$this -> countDiscounts = [ ] ;
}
public function addCombinationDiscount( CombinationDiscountInterface $cd )
{
$this -> combinationDiscounts [ ] = $cd ;
return $this ;
}
public function addCountDiscount( CountDiscount $cd )
{
$this -> countDiscounts [ ] = $cd ;
return $this ;
}
public function calculateTotalPrice( )
{
$totalPrice = 0 ;
$discountFromOrderSum = $this -> getDiscountFromOrderSum ( ) ;
foreach ( $this -> combinationDiscounts as $combinationDiscount ) {
$totalPrice += $combinationDiscount -> getSumAfterApplyingDiscount ( $this -> productCollection ) ;
}
$totalPrice += $this -> productCollection -> getSum ( ) ;
return $totalPrice - $discountFromOrderSum ;
}
private function getDiscountFromOrderSum( )
{
foreach ( $this -> countDiscounts as $countDiscount ) {
if ( $countDiscount -> match ( $this -> productCollection ) ) {
return $this -> productCollection -> getSum ( ) * $countDiscount -> getPercent ( ) ;
}
}
return 0 ;
}
}
abstract class CountCriteria
{
protected $count ;
public function __construct( $count )
{
}
abstract public function check( $givenCount ) ;
}
class EqualsCriteria extends CountCriteria
{
public function check( $givenCount )
{
return $givenCount === $this -> count ; }
}
class GreaterOrEqualsCriteria extends CountCriteria
{
public function check( $givenCount )
{
return $givenCount >= $this -> count ; }
}
// ======================================================================
$pc = new ProductCollection( [
new Product( 'a' , 100 ) ,
new Product( 'b' , 300 ) ,
new Product( 'c' , 200 ) ,
new Product( 'd' , 200 ) ,
new Product( 'e' , 100 ) ,
new Product( 'c' , 100 ) ,
] ) ;
// Тесты класса ProductCollection
assert ( $pc -> getCount ( ) === 6 ) ; assert ( $pc -> hasNames ( [ 'a' , 'c' , 'e' ] ) === true ) ; assert ( $pc -> hasNames ( [ 'a' , 'Z' ] ) === false ) ; assert ( $pc -> getOneOfName ( [ 'a' , 'Z' ] ) == true ) ; assert ( $pc -> getOneOfName ( [ 'Q' , 'Z' ] ) == false ) ; assert ( $pc -> getSum ( ) === 1000 ) ; assert ( $pc -> getWithoutNames ( [ 'a' , 'b' , 'e' ] ) -> getSum ( ) === 500 ) ;
$c = new Calculator( $pc ) ;
$c
// Если пользователь выбрал одновременно 3 продукта, он получает скидку 5% от суммы заказа
-> addCountDiscount ( new CountDiscount( new EqualsCriteria( 3 ) , 0.05 , [ 'a' , 'c' ] ) )
// Если пользователь выбрал одновременно 4 продукта, он получает скидку 10% от суммы заказа
-> addCountDiscount ( new CountDiscount( new EqualsCriteria( 4 ) , 0.1 , [ 'a' , 'c' ] ) )
// Если пользователь выбрал одновременно 5 продуктов, он получает скидку 20% от суммы заказа
-> addCountDiscount ( new CountDiscount( new GreaterOrEqualsCriteria( 5 ) , 0.2 , [ 'a' , 'c' ] ) )
;
// Суммарно - 1000
// сработал первый Discount, т.к. осталось 3 товара после того, как избавились от всех A,C
// 1000 минус 5 процентов от 1000
assert ( $c -> calculateTotalPrice ( ) == 950 ) ;
// ======================================================================
$pc2 = new ProductCollection( [
new Product( 'a' , 100 ) ,
new Product( 'a' , 100 ) ,
new Product( 'a' , 100 ) ,
new Product( 'b' , 100 ) ,
new Product( 'b' , 100 ) ,
] ) ;
$pc2 -> deleteByNames ( [ 'a' , 'a' , 'b' ] ) ;
assert ( $pc2 -> getCount ( ) === 2 ) ;
// ======================================================================
$pc3 = new ProductCollection( [
new Product( 'a' , 50 ) ,
new Product( 'b' , 50 ) ,
new Product( 'e' , 50 ) ,
new Product( 'f' , 25 ) ,
new Product( 'g' , 25 ) ,
] ) ;
$c = new Calculator( $pc3 ) ;
$c
// Если одновременно выбраны А и B, то их суммарная стоимость уменьшается на 10% (для каждой пары А и B)
-> addCombinationDiscount ( new CombinationDiscount( [ 'a' , 'b' ] , 0.1 ) )
// Если одновременно выбраны E,F,G, то их суммарная стоимость уменьшается на 5% (для каждой тройки E,F,G)
-> addCombinationDiscount ( new CombinationDiscount( [ 'e' , 'f' , 'g' ] , 0.05 ) )
;
assert ( $c -> calculateTotalPrice ( ) == 90 + 95 ) ;
// ======================================================================
$pc4 = new ProductCollection( [
new Product( 'a' , 100 ) ,
new Product( 'k' , 100 ) ,
new Product( 'p' , 100 ) ,
] ) ;
$c = new Calculator( $pc4 ) ;
$c
// Если одновременно выбраны А и один из [K,L,M], то стоимость выбранного продукта уменьшается на 5%
-> addCombinationDiscount ( new CombinationOneOfDiscount( 'a' , [ 'k' , 'l' , 'm' ] , 0.05 ) )
;
assert ( $c -> calculateTotalPrice ( ) == 100 + 95 + 100 ) ;
// ======================================================================
$pc5 = new ProductCollection( [
new Product( 'a' , 100 ) , // 1 шаг
new Product( 'b' , 100 ) , // 1 шаг
new Product( 'c' , 100 ) , // 4 шаг
new Product( 'd' , 100 ) , // 2 шаг
new Product( 'e' , 100 ) , // 2 шаг
new Product( 'f' , 100 ) , // 4 шаг
new Product( 'g' , 100 ) , // 4 шаг
new Product( 'h' , 100 ) , // 4 шаг
new Product( 'i' , 100 ) , // 4 шаг
new Product( 'j' , 100 ) , // 3 шаг
new Product( 'a' , 100 ) , // 3 шаг
] ) ;
$c = new Calculator( $pc5 ) ;
$c
-> addCountDiscount ( new CountDiscount( new EqualsCriteria( 3 ) , 0.05 , [ 'a' , 'c' ] ) )
-> addCountDiscount ( new CountDiscount( new EqualsCriteria( 4 ) , 0.1 , [ 'a' , 'c' ] ) )
-> addCountDiscount ( new CountDiscount( new GreaterOrEqualsCriteria( 5 ) , 0.2 , [ 'a' , 'c' ] ) )
-> addCombinationDiscount ( new CombinationDiscount( [ 'a' , 'b' ] , 0.1 ) )
-> addCombinationDiscount ( new CombinationDiscount( [ 'd' , 'e' ] , 0.05 ) )
-> addCombinationDiscount ( new CombinationDiscount( [ 'f' , 'e' , 'g' ] , 0.05 ) )
-> addCombinationDiscount ( new CombinationOneOfDiscount( 'a' , [ 'k' , 'j' , 'm' ] , 0.05 ) )
;
// За вычетом всех AC остаётся больше 5-и продуктов, от totalPrice нужно будет вычитать 20 процентов "суммы заказа"
// Сработал Discount для AB, теперь к totalPrice вместо 100 + 100 нужно добавить 90 + 90 = 180 (шаг 1)
// Сработал Discount для DE, к totalPrice вместо 200 нужно прибавить 95 + 95 = 190 (шаг 2)
// Не сработал Discount для FEG, так как E уже использовался
// CombinationOneOfDiscount сработал, так как есть j (шаг 3), выходит a + 5 процентов от j = 100 + 95
// Складываем оставшиеся cfghi = 5 * 100 (шаг 4)
// Считаем: 180 + 190 + 195 + 500 = 1065
// 20 процентов от 1100 это 220
assert ( $c -> calculateTotalPrice ( ) == 1065 - 220 ) ;
PD9waHAKIAovKgrQldGB0YLRjCDQv9GA0L7QtNGD0LrRgtGLIEEsIEIsIEMsIEQsIEUsIEYsIEcsIEgsIEksIEosIEssIEwsIE0uINCa0LDQttC00YvQuSDQv9GA0L7QtNGD0LrRgiDRgdGC0L7QuNGCINC+0L/RgNC10LTQtdC70LXQvdC90YPRjiDRgdGD0LzQvNGDLgrQldGB0YLRjCDQvdCw0LHQvtGAINC/0YDQsNCy0LjQuyDRgNCw0YHRh9C10YLQsCDQuNGC0L7Qs9C+0LLQvtC5INGB0YPQvNC80Ys6CtCV0YHQu9C4INC+0LTQvdC+0LLRgNC10LzQtdC90L3QviDQstGL0LHRgNCw0L3RiyDQkCDQuCBCLCDRgtC+INC40YUg0YHRg9C80LzQsNGA0L3QsNGPINGB0YLQvtC40LzQvtGB0YLRjCDRg9C80LXQvdGM0YjQsNC10YLRgdGPINC90LAgMTAlICjQtNC70Y8g0LrQsNC20LTQvtC5INC/0LDRgNGLINCQINC4IEIpCtCV0YHQu9C4INC+0LTQvdC+0LLRgNC10LzQtdC90L3QviDQstGL0LHRgNCw0L3RiyBEINC4IEUsINGC0L4g0LjRhSDRgdGD0LzQvNCw0YDQvdCw0Y8g0YHRgtC+0LjQvNC+0YHRgtGMINGD0LzQtdC90YzRiNCw0LXRgtGB0Y8g0L3QsCA1JSAo0LTQu9GPINC60LDQttC00L7QuSDQv9Cw0YDRiyBEINC4IEUpCtCV0YHQu9C4INC+0LTQvdC+0LLRgNC10LzQtdC90L3QviDQstGL0LHRgNCw0L3RiyBFLEYsRywg0YLQviDQuNGFINGB0YPQvNC80LDRgNC90LDRjyDRgdGC0L7QuNC80L7RgdGC0Ywg0YPQvNC10L3RjNGI0LDQtdGC0YHRjyDQvdCwIDUlICjQtNC70Y8g0LrQsNC20LTQvtC5INGC0YDQvtC50LrQuCBFLEYsRykK0JXRgdC70Lgg0L7QtNC90L7QstGA0LXQvNC10L3QvdC+INCy0YvQsdGA0LDQvdGLINCQINC4INC+0LTQuNC9INC40LcgW0ssTCxNXSwg0YLQviDRgdGC0L7QuNC80L7RgdGC0Ywg0LLRi9Cx0YDQsNC90L3QvtCz0L4g0L/RgNC+0LTRg9C60YLQsCDRg9C80LXQvdGM0YjQsNC10YLRgdGPINC90LAgNSUK0JXRgdC70Lgg0L/QvtC70YzQt9C+0LLQsNGC0LXQu9GMINCy0YvQsdGA0LDQuyDQvtC00L3QvtCy0YDQtdC80LXQvdC90L4gMyDQv9GA0L7QtNGD0LrRgtCwLCDQvtC9INC/0L7Qu9GD0YfQsNC10YIg0YHQutC40LTQutGDIDUlINC+0YIg0YHRg9C80LzRiyDQt9Cw0LrQsNC30LAK0JXRgdC70Lgg0L/QvtC70YzQt9C+0LLQsNGC0LXQu9GMINCy0YvQsdGA0LDQuyDQvtC00L3QvtCy0YDQtdC80LXQvdC90L4gNCDQv9GA0L7QtNGD0LrRgtCwLCDQvtC9INC/0L7Qu9GD0YfQsNC10YIg0YHQutC40LTQutGDIDEwJSDQvtGCINGB0YPQvNC80Ysg0LfQsNC60LDQt9CwCtCV0YHQu9C4INC/0L7Qu9GM0LfQvtCy0LDRgtC10LvRjCDQstGL0LHRgNCw0Lsg0L7QtNC90L7QstGA0LXQvNC10L3QvdC+IDUg0L/RgNC+0LTRg9C60YLQvtCyLCDQvtC9INC/0L7Qu9GD0YfQsNC10YIg0YHQutC40LTQutGDIDIwJSDQvtGCINGB0YPQvNC80Ysg0LfQsNC60LDQt9CwCtCe0L/QuNGB0LDQvdC90YvQtSDRgdC60LjQtNC60LggNSw2LDcg0L3QtSDRgdGD0LzQvNC40YDRg9GO0YLRgdGPLCDQv9GA0LjQvNC10L3Rj9C10YLRgdGPINGC0L7Qu9GM0LrQviDQvtC00L3QsCDQuNC3INC90LjRhQrQn9GA0L7QtNGD0LrRgtGLIEEg0LggQyDQvdC1INGD0YfQsNGB0YLQstGD0Y7RgiDQsiDRgdC60LjQtNC60LDRhSA1LDYsNwrQmtCw0LbQtNGL0Lkg0YLQvtCy0LDRgCDQvNC+0LbQtdGCINGD0YfQsNGB0YLQstC+0LLQsNGC0Ywg0YLQvtC70YzQutC+INCyINC+0LTQvdC+0Lkg0YHQutC40LTQutC1LiDQodC60LjQtNC60Lgg0L/RgNC40LzQtdC90Y/RjtGC0YHRjyDQv9C+0YHQu9C10LTQvtCy0LDRgtC10LvRjNC90L4g0LIg0L/QvtGA0Y/QtNC60LUg0L7Qv9C40YHQsNC90L3QvtC8INCy0YvRiNC1LgogCtCd0LXQvtCx0YXQvtC00LjQvNC+INC90LDQv9C40YHQsNGC0Ywg0L/RgNC+0LPRgNCw0LzQvNGDINC90LAgUEhQINGBINC40YHQv9C+0LvRjNC30L7QstCw0L3QuNC10Lwg0J7QntCfINC60L7RgtC+0YDQsNGPINC40LzQtdGPINC90LAg0LLRhdC+0LTQtSDQvdCw0LHQvtGAINC/0YDQvtC00YPQutGC0L7QsiAo0L7QtNC40L0g0L/RgNC+0LTRg9C60YIg0LzQvtC20LXRgiDQstGB0YLRgNC10YfQsNGC0YzRgdGPINC90LXRgdC60L7Qu9GM0LrQviDRgNCw0LcpINGA0LDRgdGB0YfQuNGC0YvQstCw0LvQsCDRgdGD0LzQvNCw0YDQvdGD0Y4g0LjRhSDRgdGC0L7QuNC80L7RgdGC0YwuCiovCiAKY2xhc3MgUHJvZHVjdENvbGxlY3Rpb24KewogICAgcHJpdmF0ZSAkcHJvZHVjdHM7CiAKICAgIHB1YmxpYyBmdW5jdGlvbiBfX2NvbnN0cnVjdChhcnJheSAkcHJvZHVjdHMgPSBbXSkKICAgIHsKICAgICAgICBmb3JlYWNoICgkcHJvZHVjdHMgYXMgJHByb2R1Y3QpIHsKICAgICAgICAgICAgJHRoaXMtPmFkZFByb2R1Y3QoJHByb2R1Y3QpOwogICAgICAgIH0KICAgIH0KIAogICAgcHVibGljIGZ1bmN0aW9uIGFkZFByb2R1Y3QoUHJvZHVjdCAkcHJvZHVjdCkKICAgIHsKICAgICAgICAkdGhpcy0+cHJvZHVjdHNbXSA9ICRwcm9kdWN0OwogICAgfQogCiAgICBwdWJsaWMgZnVuY3Rpb24gZ2V0QnlOYW1lKCRuYW1lKQogICAgewogICAgICAgIGZvcmVhY2ggKCR0aGlzLT5wcm9kdWN0cyBhcyAkcHJvZHVjdCkgewogICAgICAgICAgICBpZiAoJHByb2R1Y3QtPm5hbWUgPT09ICRuYW1lKSB7CiAgICAgICAgICAgICAgICByZXR1cm4gJHByb2R1Y3Q7CiAgICAgICAgICAgIH0KICAgICAgICB9CiAgICAgICAgcmV0dXJuIG51bGw7CiAgICB9CiAKICAgIHB1YmxpYyBmdW5jdGlvbiBnZXRDb3VudCgpCiAgICB7CiAgICAgICAgcmV0dXJuIGNvdW50KCR0aGlzLT5wcm9kdWN0cyk7CiAgICB9CiAKICAgIHB1YmxpYyBmdW5jdGlvbiBnZXRXaXRob3V0TmFtZXMoYXJyYXkgJHdpdGhvdXROYW1lcykKICAgIHsKICAgICAgICAkcHJvZHVjdHNXaXRob3V0TmFtZXMgPSBhcnJheV9maWx0ZXIoCiAgICAgICAgICAgICR0aGlzLT5wcm9kdWN0cywKICAgICAgICAgICAgZnVuY3Rpb24gKFByb2R1Y3QgJHApIHVzZSAoJHdpdGhvdXROYW1lcykgewogICAgICAgICAgICAgICAgcmV0dXJuICFpbl9hcnJheSgkcC0+bmFtZSwgJHdpdGhvdXROYW1lcyk7CiAgICAgICAgICAgIH0KICAgICAgICApOwogCiAgICAgICAgcmV0dXJuIG5ldyBzZWxmKCRwcm9kdWN0c1dpdGhvdXROYW1lcyk7CiAgICB9CiAKICAgIHB1YmxpYyBmdW5jdGlvbiBnZXRTdW0oKQogICAgewogICAgICAgIHJldHVybiBhcnJheV9yZWR1Y2UoCiAgICAgICAgICAgICR0aGlzLT5wcm9kdWN0cywKICAgICAgICAgICAgZnVuY3Rpb24gKCRwcmV2LCAkbmV4dCkgeyByZXR1cm4gJHByZXYgKyAkbmV4dC0+cHJpY2U7IH0sCiAgICAgICAgICAgIDAKICAgICAgICApOwogICAgfQogCiAgICBwdWJsaWMgZnVuY3Rpb24gaGFzTmFtZXMoYXJyYXkgJG5hbWVzKQogICAgewogICAgICAgICRwcm9kdWN0c05hbWVzID0gYXJyYXlfbWFwKGZ1bmN0aW9uICgkcCkgeyByZXR1cm4gJHAtPm5hbWU7IH0sICR0aGlzLT5wcm9kdWN0cyk7CiAgICAgICAgcmV0dXJuICFhcnJheV9kaWZmKCRuYW1lcywgJHByb2R1Y3RzTmFtZXMpOwogICAgfQogCiAgICBwdWJsaWMgZnVuY3Rpb24gZ2V0T25lT2ZOYW1lKGFycmF5ICRuYW1lcykKICAgIHsKICAgICAgICBmb3JlYWNoICgkdGhpcy0+cHJvZHVjdHMgYXMgJHByb2R1Y3QpIHsKICAgICAgICAgICAgaWYgKGluX2FycmF5KCRwcm9kdWN0LT5uYW1lLCAkbmFtZXMpKSB7CiAgICAgICAgICAgICAgICByZXR1cm4gJHByb2R1Y3Q7CiAgICAgICAgICAgIH0KICAgICAgICB9CiAgICAgICAgcmV0dXJuIG51bGw7CiAgICB9CiAKICAgIHB1YmxpYyBmdW5jdGlvbiBkZWxldGVCeU5hbWVzKGFycmF5ICRuYW1lcykKICAgIHsKICAgICAgICAkZGVsZXRlZCA9IFtdOwogCiAgICAgICAgZm9yZWFjaCAoJG5hbWVzIGFzICRuYW1lKSB7CiAgICAgICAgICAgICRkZWxldGVkW10gPSAkdGhpcy0+ZGVsZXRlQnlOYW1lKCRuYW1lKTsKICAgICAgICB9CiAKICAgICAgICByZXR1cm4gbmV3IHNlbGYoJGRlbGV0ZWQpOwogICAgfQogCiAgICBwdWJsaWMgZnVuY3Rpb24gZGVsZXRlQnlOYW1lKCRuYW1lKQogICAgewogICAgICAgICRwcm9kdWN0Q291bnQgPSBjb3VudCgkdGhpcy0+cHJvZHVjdHMpOwogICAgICAgIGZvciAoJGkgPSAwOyAkaSA8ICRwcm9kdWN0Q291bnQ7ICRpKyspIHsKICAgICAgICAgICAgaWYgKCR0aGlzLT5wcm9kdWN0c1skaV0tPm5hbWUgPT09ICRuYW1lKSB7CiAgICAgICAgICAgICAgICByZXR1cm4gYXJyYXlfc3BsaWNlKCR0aGlzLT5wcm9kdWN0cywgJGksIDEpWzBdOwogICAgICAgICAgICB9CiAgICAgICAgfQogICAgICAgIHJldHVybiBudWxsOwogICAgfQp9CiAKY2xhc3MgUHJvZHVjdAp7CiAgICBwdWJsaWMgJG5hbWU7CiAgICBwdWJsaWMgJHByaWNlOwogCiAgICBwdWJsaWMgZnVuY3Rpb24gX19jb25zdHJ1Y3QoJG5hbWUsICRwcmljZSkKICAgIHsKICAgICAgICAkdGhpcy0+bmFtZSA9ICRuYW1lOwogICAgICAgICR0aGlzLT5wcmljZSA9ICRwcmljZTsKICAgIH0KfQogCi8vINCh0LrQuNC00LrQsCwg0YTQvtGA0LzQuNGA0YPRjtGJ0LDRj9GB0Y8g0L3QsCDQvtGB0L3QvtCy0LUg0LrQvtC70LjRh9C10YHRgtCy0LAg0Y3Qu9C10LzQtdC90YLQvtCyINCyINGB0L/QuNGB0LrQtSDRgtC+0LLQsNGA0L7QsgpjbGFzcyBDb3VudERpc2NvdW50CnsKCXByaXZhdGUgJGNyaXRlcmlhOwoJcHJpdmF0ZSAkcGVyY2VudDsKCXByaXZhdGUgJHdpdGhvdXROYW1lczsKIAogICAgcHVibGljIGZ1bmN0aW9uIF9fY29uc3RydWN0KENvdW50Q3JpdGVyaWEgJGNyaXRlcmlhLCAkcGVyY2VudCwgYXJyYXkgJHdpdGhvdXROYW1lcyA9IFtdKQogICAgewogICAgICAgICR0aGlzLT5jcml0ZXJpYSA9ICRjcml0ZXJpYTsKICAgICAgICAkdGhpcy0+cGVyY2VudCA9ICRwZXJjZW50OwogICAgICAgICR0aGlzLT53aXRob3V0TmFtZXMgPSAkd2l0aG91dE5hbWVzOwogICAgfQogCiAgICBwdWJsaWMgZnVuY3Rpb24gbWF0Y2goUHJvZHVjdENvbGxlY3Rpb24gJHBjKQogICAgewogICAgICAgICRjb3VudFdpdGhvdXROYW1lcyA9ICRwYy0+Z2V0V2l0aG91dE5hbWVzKCR0aGlzLT53aXRob3V0TmFtZXMpLT5nZXRDb3VudCgpOwogICAgICAgIHJldHVybiAkdGhpcy0+Y3JpdGVyaWEtPmNoZWNrKCRjb3VudFdpdGhvdXROYW1lcyk7CiAgICB9CiAKICAgIHB1YmxpYyBmdW5jdGlvbiBnZXRQZXJjZW50KCkKICAgIHsKICAgICAgICByZXR1cm4gJHRoaXMtPnBlcmNlbnQ7CiAgICB9Cn0KIAppbnRlcmZhY2UgQ29tYmluYXRpb25EaXNjb3VudEludGVyZmFjZQp7CiAgICBwdWJsaWMgZnVuY3Rpb24gZ2V0U3VtQWZ0ZXJBcHBseWluZ0Rpc2NvdW50KFByb2R1Y3RDb2xsZWN0aW9uICRwcm9kdWN0Q29sbGVjdGlvbik7Cn0KIAovLyDQodC60LjQtNC60LAsINGE0L7RgNC80LjRgNGD0Y7RidCw0Y/RgdGPINC90LAg0L7RgdC90L7QstC1INC60L7QvNCx0LjQvdCw0YbQuNC4INC+0L/RgNC10LTQtdC70ZHQvdC90YvRhSDRgtC+0LLQsNGA0L7QsgpjbGFzcyBDb21iaW5hdGlvbkRpc2NvdW50IGltcGxlbWVudHMgQ29tYmluYXRpb25EaXNjb3VudEludGVyZmFjZQp7CiAgICBwcml2YXRlICRwcm9kdWN0TmFtZXM7CiAgICBwcml2YXRlICRwZXJjZW50OwogCiAgICBwdWJsaWMgZnVuY3Rpb24gX19jb25zdHJ1Y3QoYXJyYXkgJHByb2R1Y3ROYW1lcywgJHBlcmNlbnQpCiAgICB7CiAgICAgICAgJHRoaXMtPnByb2R1Y3ROYW1lcyA9ICRwcm9kdWN0TmFtZXM7CiAgICAgICAgJHRoaXMtPnBlcmNlbnQgPSAkcGVyY2VudDsKICAgIH0KIAogICAgcHVibGljIGZ1bmN0aW9uIGdldFN1bUFmdGVyQXBwbHlpbmdEaXNjb3VudChQcm9kdWN0Q29sbGVjdGlvbiAkcGMpCiAgICB7CiAgICAgICAgaWYgKCRwYy0+aGFzTmFtZXMoJHRoaXMtPnByb2R1Y3ROYW1lcykpIHsKICAgICAgICAgICAgJGRlbGV0ZWQgPSAkcGMtPmRlbGV0ZUJ5TmFtZXMoJHRoaXMtPnByb2R1Y3ROYW1lcyk7CiAgICAgICAgICAgIHJldHVybiAkZGVsZXRlZC0+Z2V0U3VtKCkgLSAkZGVsZXRlZC0+Z2V0U3VtKCkgKiAkdGhpcy0+cGVyY2VudDsKICAgICAgICB9CiAgICAgICAgcmV0dXJuIDA7CiAgICB9Cn0KIAovLyDQldGB0LvQuCDQvtC00L3QvtCy0YDQtdC80LXQvdC90L4g0LLRi9Cx0YDQsNC90Ysg0JAg0Lgg0L7QtNC40L0g0LjQtyBbSyxMLE1dLCDRgtC+INGB0YLQvtC40LzQvtGB0YLRjCDQstGL0LHRgNCw0L3QvdC+0LPQviDQv9GA0L7QtNGD0LrRgtCwLi4uCmNsYXNzIENvbWJpbmF0aW9uT25lT2ZEaXNjb3VudCBpbXBsZW1lbnRzIENvbWJpbmF0aW9uRGlzY291bnRJbnRlcmZhY2UKewogICAgcHJpdmF0ZSAkcHJvZHVjdE5hbWU7CiAgICBwcml2YXRlICRvbmVPZjsKICAgIHByaXZhdGUgJHBlcmNlbnQ7CiAKICAgIHB1YmxpYyBmdW5jdGlvbiBfX2NvbnN0cnVjdCgkcHJvZHVjdE5hbWUsIGFycmF5ICRvbmVPZiwgJHBlcmNlbnQpCiAgICB7CiAgICAgICAgJHRoaXMtPnByb2R1Y3ROYW1lID0gJHByb2R1Y3ROYW1lOwogICAgICAgICR0aGlzLT5vbmVPZiA9ICRvbmVPZjsKICAgICAgICAkdGhpcy0+cGVyY2VudCA9ICRwZXJjZW50OwogICAgfQogCiAgICBwdWJsaWMgZnVuY3Rpb24gZ2V0U3VtQWZ0ZXJBcHBseWluZ0Rpc2NvdW50KFByb2R1Y3RDb2xsZWN0aW9uICRwYykKICAgIHsKICAgICAgICAkb25lT2YgPSAkcGMtPmdldE9uZU9mTmFtZSgkdGhpcy0+b25lT2YpOwogICAgICAgICRwcm9kdWN0ID0gJHBjLT5nZXRCeU5hbWUoJHRoaXMtPnByb2R1Y3ROYW1lKTsKICAgICAgICBpZiAoJG9uZU9mICYmICRwYy0+aGFzTmFtZXMoWyR0aGlzLT5wcm9kdWN0TmFtZV0pKSB7CiAgICAgICAgICAgICRwYy0+ZGVsZXRlQnlOYW1lcyhbJG9uZU9mLT5uYW1lLCAkcHJvZHVjdC0+bmFtZV0pOwogICAgICAgICAgICByZXR1cm4gJHByb2R1Y3QtPnByaWNlICsgKCRvbmVPZi0+cHJpY2UgLSAkb25lT2YtPnByaWNlICogJHRoaXMtPnBlcmNlbnQpOwogICAgICAgIH0KICAgICAgICByZXR1cm4gMDsKICAgIH0KfQogCmNsYXNzIENhbGN1bGF0b3IKewogICAgcHJpdmF0ZSAkY29tYmluYXRpb25EaXNjb3VudHM7CiAgICBwcml2YXRlICRwcm9kdWN0Q29sbGVjdGlvbjsKICAgIHByaXZhdGUgJGNvdW50RGlzY291bnRzOwogCiAgICBwdWJsaWMgZnVuY3Rpb24gX19jb25zdHJ1Y3QoUHJvZHVjdENvbGxlY3Rpb24gJHByb2R1Y3RDb2xsZWN0aW9uKQogICAgewogICAgICAgICR0aGlzLT5wcm9kdWN0Q29sbGVjdGlvbiA9ICRwcm9kdWN0Q29sbGVjdGlvbjsKICAgICAgICAkdGhpcy0+Y29tYmluYXRpb25EaXNjb3VudHMgPSBbXTsKICAgICAgICAkdGhpcy0+Y291bnREaXNjb3VudHMgPSBbXTsKICAgIH0KIAogICAgcHVibGljIGZ1bmN0aW9uIGFkZENvbWJpbmF0aW9uRGlzY291bnQoQ29tYmluYXRpb25EaXNjb3VudEludGVyZmFjZSAkY2QpCiAgICB7CiAgICAgICAgJHRoaXMtPmNvbWJpbmF0aW9uRGlzY291bnRzW10gPSAkY2Q7CiAgICAgICAgcmV0dXJuICR0aGlzOwogICAgfQogCiAgICBwdWJsaWMgZnVuY3Rpb24gYWRkQ291bnREaXNjb3VudChDb3VudERpc2NvdW50ICRjZCkKICAgIHsKICAgICAgICAkdGhpcy0+Y291bnREaXNjb3VudHNbXSA9ICRjZDsKICAgICAgICByZXR1cm4gJHRoaXM7CiAgICB9CiAKICAgIHB1YmxpYyBmdW5jdGlvbiBjYWxjdWxhdGVUb3RhbFByaWNlKCkKICAgIHsKICAgICAgICAkdG90YWxQcmljZSA9IDA7CiAgICAgICAgJGRpc2NvdW50RnJvbU9yZGVyU3VtID0gJHRoaXMtPmdldERpc2NvdW50RnJvbU9yZGVyU3VtKCk7CgogICAgICAgIGZvcmVhY2ggKCR0aGlzLT5jb21iaW5hdGlvbkRpc2NvdW50cyBhcyAkY29tYmluYXRpb25EaXNjb3VudCkgewogICAgICAgICAgICAkdG90YWxQcmljZSArPSAkY29tYmluYXRpb25EaXNjb3VudC0+Z2V0U3VtQWZ0ZXJBcHBseWluZ0Rpc2NvdW50KCR0aGlzLT5wcm9kdWN0Q29sbGVjdGlvbik7CiAgICAgICAgfQogCiAgICAgICAgJHRvdGFsUHJpY2UgKz0gJHRoaXMtPnByb2R1Y3RDb2xsZWN0aW9uLT5nZXRTdW0oKTsKIAogICAgICAgIHJldHVybiAkdG90YWxQcmljZSAtICRkaXNjb3VudEZyb21PcmRlclN1bTsKICAgIH0KIAogICAgcHJpdmF0ZSBmdW5jdGlvbiBnZXREaXNjb3VudEZyb21PcmRlclN1bSgpCiAgICB7CiAgICAgICAgZm9yZWFjaCAoJHRoaXMtPmNvdW50RGlzY291bnRzIGFzICRjb3VudERpc2NvdW50KSB7CiAgICAgICAgICAgIGlmICgkY291bnREaXNjb3VudC0+bWF0Y2goJHRoaXMtPnByb2R1Y3RDb2xsZWN0aW9uKSkgewogICAgICAgICAgICAgICAgcmV0dXJuICR0aGlzLT5wcm9kdWN0Q29sbGVjdGlvbi0+Z2V0U3VtKCkgKiAkY291bnREaXNjb3VudC0+Z2V0UGVyY2VudCgpOwogICAgICAgICAgICB9CiAgICAgICAgfQoKICAgICAgICByZXR1cm4gMDsKICAgIH0KfQoKYWJzdHJhY3QgY2xhc3MgQ291bnRDcml0ZXJpYQp7CiAgICBwcm90ZWN0ZWQgJGNvdW50OwoKICAgIHB1YmxpYyBmdW5jdGlvbiBfX2NvbnN0cnVjdCgkY291bnQpCiAgICB7CiAgICAgICAgJHRoaXMtPmNvdW50ID0gJGNvdW50OwogICAgfQoKICAgIGFic3RyYWN0IHB1YmxpYyBmdW5jdGlvbiBjaGVjaygkZ2l2ZW5Db3VudCk7Cn0KCmNsYXNzIEVxdWFsc0NyaXRlcmlhIGV4dGVuZHMgQ291bnRDcml0ZXJpYQp7CiAgICBwdWJsaWMgZnVuY3Rpb24gY2hlY2soJGdpdmVuQ291bnQpCiAgICB7CiAgICAgICAgcmV0dXJuICRnaXZlbkNvdW50ID09PSAkdGhpcy0+Y291bnQ7CiAgICB9Cn0KCmNsYXNzIEdyZWF0ZXJPckVxdWFsc0NyaXRlcmlhIGV4dGVuZHMgQ291bnRDcml0ZXJpYQp7CiAgICBwdWJsaWMgZnVuY3Rpb24gY2hlY2soJGdpdmVuQ291bnQpCiAgICB7CiAgICAgICAgcmV0dXJuICRnaXZlbkNvdW50ID49ICR0aGlzLT5jb3VudDsKICAgIH0KfQovLyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09CiAKJHBjID0gbmV3IFByb2R1Y3RDb2xsZWN0aW9uKFsKICAgIG5ldyBQcm9kdWN0KCdhJywgMTAwKSwKICAgIG5ldyBQcm9kdWN0KCdiJywgMzAwKSwKICAgIG5ldyBQcm9kdWN0KCdjJywgMjAwKSwKICAgIG5ldyBQcm9kdWN0KCdkJywgMjAwKSwKICAgIG5ldyBQcm9kdWN0KCdlJywgMTAwKSwKICAgIG5ldyBQcm9kdWN0KCdjJywgMTAwKSwKXSk7CiAKLy8g0KLQtdGB0YLRiyDQutC70LDRgdGB0LAgUHJvZHVjdENvbGxlY3Rpb24KYXNzZXJ0KCRwYy0+Z2V0Q291bnQoKSA9PT0gNik7CmFzc2VydCgkcGMtPmhhc05hbWVzKFsnYScsICdjJywgJ2UnXSkgPT09IHRydWUpOwphc3NlcnQoJHBjLT5oYXNOYW1lcyhbJ2EnLCAnWiddKSA9PT0gZmFsc2UpOwphc3NlcnQoJHBjLT5nZXRPbmVPZk5hbWUoWydhJywgJ1onXSkgPT0gdHJ1ZSk7CmFzc2VydCgkcGMtPmdldE9uZU9mTmFtZShbJ1EnLCAnWiddKSA9PSBmYWxzZSk7CmFzc2VydCgkcGMtPmdldFN1bSgpID09PSAxMDAwKTsKYXNzZXJ0KCRwYy0+Z2V0V2l0aG91dE5hbWVzKFsnYScsICdiJywgJ2UnXSktPmdldFN1bSgpID09PSA1MDApOwogCiRjID0gbmV3IENhbGN1bGF0b3IoJHBjKTsKJGMKICAgIC8vINCV0YHQu9C4INC/0L7Qu9GM0LfQvtCy0LDRgtC10LvRjCDQstGL0LHRgNCw0Lsg0L7QtNC90L7QstGA0LXQvNC10L3QvdC+IDMg0L/RgNC+0LTRg9C60YLQsCwg0L7QvSDQv9C+0LvRg9GH0LDQtdGCINGB0LrQuNC00LrRgyA1JSDQvtGCINGB0YPQvNC80Ysg0LfQsNC60LDQt9CwCiAgICAtPmFkZENvdW50RGlzY291bnQobmV3IENvdW50RGlzY291bnQobmV3IEVxdWFsc0NyaXRlcmlhKDMpLCAwLjA1LCBbJ2EnLCAnYyddKSkKICAgIC8vINCV0YHQu9C4INC/0L7Qu9GM0LfQvtCy0LDRgtC10LvRjCDQstGL0LHRgNCw0Lsg0L7QtNC90L7QstGA0LXQvNC10L3QvdC+IDQg0L/RgNC+0LTRg9C60YLQsCwg0L7QvSDQv9C+0LvRg9GH0LDQtdGCINGB0LrQuNC00LrRgyAxMCUg0L7RgiDRgdGD0LzQvNGLINC30LDQutCw0LfQsAogICAgLT5hZGRDb3VudERpc2NvdW50KG5ldyBDb3VudERpc2NvdW50KG5ldyBFcXVhbHNDcml0ZXJpYSg0KSwgMC4xLCBbJ2EnLCAnYyddKSkKICAgIC8vINCV0YHQu9C4INC/0L7Qu9GM0LfQvtCy0LDRgtC10LvRjCDQstGL0LHRgNCw0Lsg0L7QtNC90L7QstGA0LXQvNC10L3QvdC+IDUg0L/RgNC+0LTRg9C60YLQvtCyLCDQvtC9INC/0L7Qu9GD0YfQsNC10YIg0YHQutC40LTQutGDIDIwJSDQvtGCINGB0YPQvNC80Ysg0LfQsNC60LDQt9CwCiAgICAtPmFkZENvdW50RGlzY291bnQobmV3IENvdW50RGlzY291bnQobmV3IEdyZWF0ZXJPckVxdWFsc0NyaXRlcmlhKDUpLCAwLjIsIFsnYScsICdjJ10pKQo7CiAKLy8g0KHRg9C80LzQsNGA0L3QviAtIDEwMDAKLy8g0YHRgNCw0LHQvtGC0LDQuyDQv9C10YDQstGL0LkgRGlzY291bnQsINGCLtC6LiDQvtGB0YLQsNC70L7RgdGMIDMg0YLQvtCy0LDRgNCwINC/0L7RgdC70LUg0YLQvtCz0L4sINC60LDQuiDQuNC30LHQsNCy0LjQu9C40YHRjCDQvtGCINCy0YHQtdGFIEEsQwovLyAxMDAwINC80LjQvdGD0YEgNSDQv9GA0L7RhtC10L3RgtC+0LIg0L7RgiAxMDAwCmFzc2VydCgkYy0+Y2FsY3VsYXRlVG90YWxQcmljZSgpID09IDk1MCk7CgoKLy8gPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQogCiRwYzIgPSBuZXcgUHJvZHVjdENvbGxlY3Rpb24oWwogICAgbmV3IFByb2R1Y3QoJ2EnLCAxMDApLAogICAgbmV3IFByb2R1Y3QoJ2EnLCAxMDApLAogICAgbmV3IFByb2R1Y3QoJ2EnLCAxMDApLAogICAgbmV3IFByb2R1Y3QoJ2InLCAxMDApLAogICAgbmV3IFByb2R1Y3QoJ2InLCAxMDApLApdKTsKIAokcGMyLT5kZWxldGVCeU5hbWVzKFsnYScsICdhJywgJ2InXSk7CmFzc2VydCgkcGMyLT5nZXRDb3VudCgpID09PSAyKTsKIAovLyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09CiAKJHBjMyA9IG5ldyBQcm9kdWN0Q29sbGVjdGlvbihbCiAgICBuZXcgUHJvZHVjdCgnYScsIDUwKSwKICAgIG5ldyBQcm9kdWN0KCdiJywgNTApLAogICAgbmV3IFByb2R1Y3QoJ2UnLCA1MCksCiAgICBuZXcgUHJvZHVjdCgnZicsIDI1KSwKICAgIG5ldyBQcm9kdWN0KCdnJywgMjUpLApdKTsKIAokYyA9IG5ldyBDYWxjdWxhdG9yKCRwYzMpOwokYwogICAgLy8g0JXRgdC70Lgg0L7QtNC90L7QstGA0LXQvNC10L3QvdC+INCy0YvQsdGA0LDQvdGLINCQINC4IEIsINGC0L4g0LjRhSDRgdGD0LzQvNCw0YDQvdCw0Y8g0YHRgtC+0LjQvNC+0YHRgtGMINGD0LzQtdC90YzRiNCw0LXRgtGB0Y8g0L3QsCAxMCUgKNC00LvRjyDQutCw0LbQtNC+0Lkg0L/QsNGA0Ysg0JAg0LggQikKICAgIC0+YWRkQ29tYmluYXRpb25EaXNjb3VudChuZXcgQ29tYmluYXRpb25EaXNjb3VudChbJ2EnLCAnYiddLCAwLjEpKQogICAgLy8g0JXRgdC70Lgg0L7QtNC90L7QstGA0LXQvNC10L3QvdC+INCy0YvQsdGA0LDQvdGLIEUsRixHLCDRgtC+INC40YUg0YHRg9C80LzQsNGA0L3QsNGPINGB0YLQvtC40LzQvtGB0YLRjCDRg9C80LXQvdGM0YjQsNC10YLRgdGPINC90LAgNSUgKNC00LvRjyDQutCw0LbQtNC+0Lkg0YLRgNC+0LnQutC4IEUsRixHKQogICAgLT5hZGRDb21iaW5hdGlvbkRpc2NvdW50KG5ldyBDb21iaW5hdGlvbkRpc2NvdW50KFsnZScsICdmJywgJ2cnXSwgMC4wNSkpCjsKIAphc3NlcnQoJGMtPmNhbGN1bGF0ZVRvdGFsUHJpY2UoKSA9PSA5MCArIDk1KTsKIAovLyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09CiAKJHBjNCA9IG5ldyBQcm9kdWN0Q29sbGVjdGlvbihbCiAgICBuZXcgUHJvZHVjdCgnYScsIDEwMCksCiAgICBuZXcgUHJvZHVjdCgnaycsIDEwMCksCiAgICBuZXcgUHJvZHVjdCgncCcsIDEwMCksCl0pOwogCiRjID0gbmV3IENhbGN1bGF0b3IoJHBjNCk7CiRjCiAgICAvLyDQldGB0LvQuCDQvtC00L3QvtCy0YDQtdC80LXQvdC90L4g0LLRi9Cx0YDQsNC90Ysg0JAg0Lgg0L7QtNC40L0g0LjQtyBbSyxMLE1dLCDRgtC+INGB0YLQvtC40LzQvtGB0YLRjCDQstGL0LHRgNCw0L3QvdC+0LPQviDQv9GA0L7QtNGD0LrRgtCwINGD0LzQtdC90YzRiNCw0LXRgtGB0Y8g0L3QsCA1JQogICAgLT5hZGRDb21iaW5hdGlvbkRpc2NvdW50KG5ldyBDb21iaW5hdGlvbk9uZU9mRGlzY291bnQoJ2EnLCBbJ2snLCAnbCcsICdtJ10sIDAuMDUpKQo7CiAKYXNzZXJ0KCRjLT5jYWxjdWxhdGVUb3RhbFByaWNlKCkgPT0gMTAwICsgOTUgKyAxMDApOwoKCgovLyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09CiAKJHBjNSA9IG5ldyBQcm9kdWN0Q29sbGVjdGlvbihbCiAgICBuZXcgUHJvZHVjdCgnYScsIDEwMCksIC8vIDEg0YjQsNCzCiAgICBuZXcgUHJvZHVjdCgnYicsIDEwMCksIC8vIDEg0YjQsNCzCiAgICBuZXcgUHJvZHVjdCgnYycsIDEwMCksIC8vIDQg0YjQsNCzCiAgICBuZXcgUHJvZHVjdCgnZCcsIDEwMCksIC8vIDIg0YjQsNCzIAogICAgbmV3IFByb2R1Y3QoJ2UnLCAxMDApLCAvLyAyINGI0LDQswogICAgbmV3IFByb2R1Y3QoJ2YnLCAxMDApLCAvLyA0INGI0LDQswogICAgbmV3IFByb2R1Y3QoJ2cnLCAxMDApLCAvLyA0INGI0LDQswogICAgbmV3IFByb2R1Y3QoJ2gnLCAxMDApLCAvLyA0INGI0LDQswogICAgbmV3IFByb2R1Y3QoJ2knLCAxMDApLCAvLyA0INGI0LDQswogICAgbmV3IFByb2R1Y3QoJ2onLCAxMDApLCAvLyAzINGI0LDQswogICAgbmV3IFByb2R1Y3QoJ2EnLCAxMDApLCAvLyAzINGI0LDQswpdKTsKCiRjID0gbmV3IENhbGN1bGF0b3IoJHBjNSk7CiRjCiAgICAtPmFkZENvdW50RGlzY291bnQobmV3IENvdW50RGlzY291bnQobmV3IEVxdWFsc0NyaXRlcmlhKDMpLCAwLjA1LCBbJ2EnLCAnYyddKSkKICAgIC0+YWRkQ291bnREaXNjb3VudChuZXcgQ291bnREaXNjb3VudChuZXcgRXF1YWxzQ3JpdGVyaWEoNCksIDAuMSwgWydhJywgJ2MnXSkpCiAgICAtPmFkZENvdW50RGlzY291bnQobmV3IENvdW50RGlzY291bnQobmV3IEdyZWF0ZXJPckVxdWFsc0NyaXRlcmlhKDUpLCAwLjIsIFsnYScsICdjJ10pKQogICAgLT5hZGRDb21iaW5hdGlvbkRpc2NvdW50KG5ldyBDb21iaW5hdGlvbkRpc2NvdW50KFsnYScsICdiJ10sIDAuMSkpCiAgICAtPmFkZENvbWJpbmF0aW9uRGlzY291bnQobmV3IENvbWJpbmF0aW9uRGlzY291bnQoWydkJywgJ2UnXSwgMC4wNSkpCiAgICAtPmFkZENvbWJpbmF0aW9uRGlzY291bnQobmV3IENvbWJpbmF0aW9uRGlzY291bnQoWydmJywgJ2UnLCAnZyddLCAwLjA1KSkKICAgIC0+YWRkQ29tYmluYXRpb25EaXNjb3VudChuZXcgQ29tYmluYXRpb25PbmVPZkRpc2NvdW50KCdhJywgWydrJywgJ2onLCAnbSddLCAwLjA1KSkKOwogCi8vINCX0LAg0LLRi9GH0LXRgtC+0Lwg0LLRgdC10YUgQUMg0L7RgdGC0LDRkdGC0YHRjyDQsdC+0LvRjNGI0LUgNS3QuCDQv9GA0L7QtNGD0LrRgtC+0LIsINC+0YIgdG90YWxQcmljZSDQvdGD0LbQvdC+INCx0YPQtNC10YIg0LLRi9GH0LjRgtCw0YLRjCAyMCDQv9GA0L7RhtC10L3RgtC+0LIgItGB0YPQvNC80Ysg0LfQsNC60LDQt9CwIgovLyDQodGA0LDQsdC+0YLQsNC7IERpc2NvdW50INC00LvRjyBBQiwg0YLQtdC/0LXRgNGMINC6IHRvdGFsUHJpY2Ug0LLQvNC10YHRgtC+IDEwMCArIDEwMCDQvdGD0LbQvdC+INC00L7QsdCw0LLQuNGC0YwgOTAgKyA5MCA9IDE4MCAo0YjQsNCzIDEpCi8vINCh0YDQsNCx0L7RgtCw0LsgRGlzY291bnQg0LTQu9GPIERFLCDQuiB0b3RhbFByaWNlINCy0LzQtdGB0YLQviAyMDAg0L3Rg9C20L3QviDQv9GA0LjQsdCw0LLQuNGC0YwgOTUgKyA5NSA9IDE5MCAo0YjQsNCzIDIpCi8vINCd0LUg0YHRgNCw0LHQvtGC0LDQuyBEaXNjb3VudCDQtNC70Y8gRkVHLCDRgtCw0Log0LrQsNC6IEUg0YPQttC1INC40YHQv9C+0LvRjNC30L7QstCw0LvRgdGPCi8vIENvbWJpbmF0aW9uT25lT2ZEaXNjb3VudCDRgdGA0LDQsdC+0YLQsNC7LCDRgtCw0Log0LrQsNC6INC10YHRgtGMIGogKNGI0LDQsyAzKSwg0LLRi9GF0L7QtNC40YIgYSArIDUg0L/RgNC+0YbQtdC90YLQvtCyINC+0YIgaiA9IDEwMCArIDk1Ci8vINCh0LrQu9Cw0LTRi9Cy0LDQtdC8INC+0YHRgtCw0LLRiNC40LXRgdGPIGNmZ2hpID0gNSAqIDEwMCAo0YjQsNCzIDQpCi8vINCh0YfQuNGC0LDQtdC8OiAxODAgKyAxOTAgKyAxOTUgKyA1MDAgPSAxMDY1Ci8vIDIwINC/0YDQvtGG0LXQvdGC0L7QsiDQvtGCIDExMDAg0Y3RgtC+IDIyMAphc3NlcnQoJGMtPmNhbGN1bGF0ZVRvdGFsUHJpY2UoKSA9PSAxMDY1IC0gMjIwKTs=