<?php
throw new ErrorException($errstr, $errno, 0, $errfile, $errline);
});
class Calculator
{
/**
* @param DiscountCollection $discountCollection
* @param ProductCollection $productCollection
* @return int|float
*/
public function calculateTotalPrice(
DiscountCollection $discountCollection,
ProductCollection $productCollection
) {
$pricesSum = function ($p, $n) { return $p + $n->price; };
$usedProducts = new ProductCollection();
$totalPrice = 0;
foreach ($discountCollection as $discount) {
$notUsedProducts = $productCollection->difference($usedProducts);
$discountResult = $discount->getDiscountResult($notUsedProducts);
$matchedProducts = $discountResult->getMatchedProducts();
$matchedPrice = $matchedProducts->reduce($pricesSum, 0);
$totalPrice += $matchedPrice - $matchedPrice * $discountResult->getPercent();
$usedProducts = $usedProducts->merge($matchedProducts);
}
$totalPrice += $productCollection->difference($usedProducts)->reduce($pricesSum, 0);
return $totalPrice;
}
}
interface DiscountInterface
{
/**
* @param ProductCollection $notUsedProducts
* @return DiscountResult
*/
public function getDiscountResult(ProductCollection $notUsedProducts);
}
class CombinationDiscount implements DiscountInterface
{
private $names;
private $percent;
public function __construct
(array $names, $percent) {
$this->names = $names;
$this->percent = $percent;
}
/**
* @param ProductCollection $notUsedProducts
* @return DiscountResult
*/
public function getDiscountResult(ProductCollection $notUsedProducts) {
$productsUsedInThisDiscount = new ProductCollection();
foreach ($this->names as $name) {
$matchedProduct = $notUsedProducts->getFirstByName($name);
if ($matchedProduct) {
$notUsedProducts->removeProduct($matchedProduct);
$productsUsedInThisDiscount->addProduct($matchedProduct);
} else {
return new DiscountResult(new ProductCollection(), $this->percent);
}
}
return new DiscountResult($productsUsedInThisDiscount, $this->percent);
}
}
class CombinationOneOfDiscount implements DiscountInterface
{
private $oneOfNames;
private $percent;
private $productName;
public function __construct
($productName, array $oneOfNames, $percent) {
$this->oneOfNames = $oneOfNames;
$this->percent = $percent;
$this->productName = $productName;
}
/**
* @param ProductCollection $notUsedProducts
* @return DiscountResult
*/
public function getDiscountResult(ProductCollection $notUsedProducts) {
$product = $notUsedProducts->getFirstByName($this->productName);
$firstByName = $notUsedProducts->getFirstByNames($this->oneOfNames);
if ($product && $firstByName) {
return new DiscountResult(new ProductCollection([$product, $firstByName]), $this->percent);
}
return new DiscountResult(new ProductCollection(), 0);
}
}
class CountDiscount implements DiscountInterface
{
private $exceptNames;
private $countPercentMap;
public function __construct
(array $countPercentMap, array $exceptNames = []) {
$this->countPercentMap = $countPercentMap;
$this->exceptNames = $exceptNames;
}
/**
* @param ProductCollection $notUsedProducts
* @return DiscountResult
*/
public function getDiscountResult(ProductCollection $notUsedProducts) {
$matchedProducts = $notUsedProducts->getAllExceptNames($this->exceptNames);
$countMatched = $matchedProducts->count();
return new DiscountResult($matchedProducts, $this->countPercentMap[$countMatched]);
}
if ($countMatched > $maxCount) {
return new DiscountResult($matchedProducts, $this->countPercentMap[$maxCount]);
}
return new DiscountResult(new ProductCollection(), 0);
}
}
class DiscountCollection implements IteratorAggregate
{
/**
* @var DiscountInterface[]
*/
private $discounts;
public function __construct
(array $discounts = []) {
$this->discounts = [];
foreach ($discounts as $discount) {
$this->addDiscount($discount);
}
}
public function addDiscount(DiscountInterface $discount)
{
$this->discounts[] = $discount;
}
/**
* @return ArrayIterator|DiscountInterface[]
*/
public function getIterator()
{
return new ArrayIterator($this->discounts);
}
}
class ProductCollection implements IteratorAggregate
{
private $products;
public function __construct
(array $products = []) {
$this->products = [];
foreach ($products as $product) {
$this->addProduct($product);
}
}
public function addProduct(Product $product)
{
$this->products[] = $product;
}
/**
* @param Product $product
* @return Product|false
*/
public function removeProduct(Product $product)
{
for ($i = 0; $i < count($this->products); $i++) { if ($this->products[$i] === $product) {
}
}
return false;
}
public function reduce(callable $callable, $initial)
{
}
/**
* @param ProductCollection $productCollection
* @return ProductCollection
*/
public function merge(ProductCollection $productCollection)
{
return new self(array_merge($this->products, $productCollection->toArray())); }
/**
* @param ProductCollection $productCollection
* @return ProductCollection
*/
public function difference(ProductCollection $productCollection)
{
$pc = new self($this->products);
foreach ($productCollection as $product) {
$pc->removeProduct($product);
}
return $pc;
}
/**
* @param $name
* @return Product|null
*/
public function getFirstByName($name)
{
foreach ($this->products as $product) {
if ($product->name === $name) {
return $product;
}
}
return null;
}
/**
* @param array $names
* @return Product|null
*/
public function getFirstByNames
(array $names) {
foreach ($names as $name) {
$product = $this->getFirstByName($name);
if ($product) {
return $product;
}
}
return null;
}
/**
* @param array $names
* @return ProductCollection
*/
public function getAllExceptNames
(array $names) {
$this->products,
function (Product $p) use ($names) {
return !in_array($p->name, $names, true); }
);
return new self($allExceptNames);
}
public function toArray()
{
return $this->products;
}
/**
* @param Product $product
* @return bool
*/
public function containsProduct(Product $product)
{
return in_array($product, $this->products, true); }
/**
* @return bool
*/
public function isEmpty()
{
return count($this->products) === 0; }
/**
* @return int
*/
{
return count($this->products); }
/**
* @return ArrayIterator|Product[]
*/
public function getIterator()
{
return new ArrayIterator($this->products);
}
}
class DiscountResult
{
/**
* @var ProductCollection
*/
private $matchedProducts;
private $percent;
public function __construct(ProductCollection $matchedProducts, $percent)
{
$this->matchedProducts = $matchedProducts;
$this->percent = $percent;
}
/**
* @return ProductCollection
*/
public function getMatchedProducts()
{
return $this->matchedProducts;
}
public function getPercent()
{
return $this->percent;
}
}
class Product
{
public $name;
public $price;
public function __construct($name, $price)
{
$this->name = $name;
$this->price = $price;
}
}
$a = new Product('a', 100);
$a2 = new Product('a', 100);
$b = new Product('b', 300);
$c = new Product('c', 200);
$d = new Product('d', 200);
$e = new Product('e', 100);
$c2 = new Product('c', 100);
// ProductCollection
$pc = new ProductCollection([$a, $a2, $b, $c, $d, $e]);
assert($pc->containsProduct($a)); assert($pc->containsProduct(new Product
('a', 1)) === false); assert($pc->getFirstByName('a') === $a); assert($pc->removeProduct(new Product
('a', 1)) === false); assert($pc->removeProduct($a) === $a); assert($pc->containsProduct($a) === false); assert($pc->getFirstByName('a') === $a2); assert($pc->merge(new ProductCollection
([$c2, $a])) == new ProductCollection
([$a2, $b, $c, $d, $e, $c2, $a])); assert((new ProductCollection
([$a, $b, $c]))->difference(new ProductCollection
([$a, $c])) == new ProductCollection
([$b]));
// CombinationDiscount
$cd = new CombinationDiscount(['a', 'b'], 0.5);
$allProducts = new ProductCollection([$a, $b, $c, $d, $e, $a2]);
$notUsedProducts1 = $allProducts->difference(new ProductCollection([]));
$notUsedProducts2 = $allProducts->difference(new ProductCollection([$a]));
$notUsedProducts3 = $allProducts->difference(new ProductCollection([$a2, $a]));
assert($cd->getDiscountResult($notUsedProducts1)->getMatchedProducts() == new ProductCollection
([$a, $b])); assert($cd->getDiscountResult($notUsedProducts2)->getMatchedProducts() == new ProductCollection
([$a2, $b])); assert($cd->getDiscountResult($notUsedProducts3)->getMatchedProducts()->isEmpty());
// Calculator with CombinationDiscount
$c = new Calculator();
assert(200 == $c->calculateTotalPrice(new DiscountCollection
([$cd]), new ProductCollection
([ new Product('a', 100),
new Product('a', 100),
])));
assert(100 == $c->calculateTotalPrice(new DiscountCollection
([$cd]), new ProductCollection
([ new Product('a', 100),
new Product('b', 100),
])));
$discountCollection = new DiscountCollection([
new CombinationDiscount(['a', 'b', 'c'], 0.2),
new CombinationDiscount(['a', 'b', 'c', 'd'], 0.8),
]);
$abcdCollection = new ProductCollection([
new Product('a', 100),
new Product('b', 100),
new Product('c', 100),
new Product('d', 100),
]);
assert(100 + 300 - 300 * 0.2 == $c->calculateTotalPrice($discountCollection, $abcdCollection));
$discountCollection = new DiscountCollection([
new CombinationDiscount(['a', 'b', 'c', 'd'], 0.8),
new CombinationDiscount(['a', 'b', 'c'], 0.2),
]);
assert(400 - 400 * 0.8 == $c->calculateTotalPrice($discountCollection, $abcdCollection));
// CountDiscount
$cd = new CountDiscount([
1 => 0.3,
2 => 0.4,
3 => 0.5,
], ['a', 'b']);
assert(200 - 200 * 0.4 + 200 == $c->calculateTotalPrice(new DiscountCollection
([$cd]), $abcdCollection)); assert(200 == $c->calculateTotalPrice(new DiscountCollection
([new CountDiscount
([2 => 0.5])]), $abcdCollection));
// CombinationOneOfDiscount
$cod = new CombinationOneOfDiscount('a', ['b', 'c', 'd'], 0.5);
assert(200 + 100 == $c->calculateTotalPrice(new DiscountCollection
([$cod]), $abcdCollection));
// Main test
$discountCollection = new DiscountCollection([
new CombinationDiscount(['a', 'b'], 0.1),
new CombinationDiscount(['d', 'e'], 0.05),
new CombinationDiscount(['f', 'e', 'g'], 0.05),
new CombinationOneOfDiscount('a', ['k', 'j', 'm'], 0.05),
new CountDiscount([3 => 0.05, 4 => 0.01, 5 => 0.02], ['a', 'c']),
]);
$productCollection = new ProductCollection([
new Product('a', 100),
new Product('b', 100),
new Product('c', 100),
new Product('d', 100),
new Product('e', 100),
new Product('f', 100),
new Product('g', 100),
new Product('h', 100),
new Product('i', 100),
new Product('j', 100),
new Product('a', 100),
]);
$answer = (200 - 200 * 0.1) + (200 - 200 * 0.05) + (200 - 200 * 0.05) + (400 - 400 * 0.01) + 100;
assert($answer == $c->calculateTotalPrice($discountCollection, $productCollection));
PD9waHAKCnNldF9lcnJvcl9oYW5kbGVyKGZ1bmN0aW9uICgkZXJybm8sICRlcnJzdHIsICRlcnJmaWxlLCAkZXJybGluZSApIHsKICAgIHRocm93IG5ldyBFcnJvckV4Y2VwdGlvbigkZXJyc3RyLCAkZXJybm8sIDAsICRlcnJmaWxlLCAkZXJybGluZSk7Cn0pOwoKY2xhc3MgQ2FsY3VsYXRvcgp7CiAgICAvKioKICAgICAqIEBwYXJhbSBEaXNjb3VudENvbGxlY3Rpb24gJGRpc2NvdW50Q29sbGVjdGlvbgogICAgICogQHBhcmFtIFByb2R1Y3RDb2xsZWN0aW9uICRwcm9kdWN0Q29sbGVjdGlvbgogICAgICogQHJldHVybiBpbnR8ZmxvYXQKICAgICAqLwogICAgcHVibGljIGZ1bmN0aW9uIGNhbGN1bGF0ZVRvdGFsUHJpY2UoCiAgICAgICAgRGlzY291bnRDb2xsZWN0aW9uICRkaXNjb3VudENvbGxlY3Rpb24sCiAgICAgICAgUHJvZHVjdENvbGxlY3Rpb24gJHByb2R1Y3RDb2xsZWN0aW9uCiAgICApIHsKICAgICAgICAkcHJpY2VzU3VtID0gZnVuY3Rpb24gKCRwLCAkbikgeyByZXR1cm4gJHAgKyAkbi0+cHJpY2U7IH07CiAgICAgICAgJHVzZWRQcm9kdWN0cyA9IG5ldyBQcm9kdWN0Q29sbGVjdGlvbigpOwogICAgICAgICR0b3RhbFByaWNlID0gMDsKCiAgICAgICAgZm9yZWFjaCAoJGRpc2NvdW50Q29sbGVjdGlvbiBhcyAkZGlzY291bnQpIHsKICAgICAgICAgICAgJG5vdFVzZWRQcm9kdWN0cyA9ICRwcm9kdWN0Q29sbGVjdGlvbi0+ZGlmZmVyZW5jZSgkdXNlZFByb2R1Y3RzKTsKICAgICAgICAgICAgJGRpc2NvdW50UmVzdWx0ID0gJGRpc2NvdW50LT5nZXREaXNjb3VudFJlc3VsdCgkbm90VXNlZFByb2R1Y3RzKTsKICAgICAgICAgICAgJG1hdGNoZWRQcm9kdWN0cyA9ICRkaXNjb3VudFJlc3VsdC0+Z2V0TWF0Y2hlZFByb2R1Y3RzKCk7CiAgICAgICAgICAgICRtYXRjaGVkUHJpY2UgPSAkbWF0Y2hlZFByb2R1Y3RzLT5yZWR1Y2UoJHByaWNlc1N1bSwgMCk7CiAgICAgICAgICAgICR0b3RhbFByaWNlICs9ICRtYXRjaGVkUHJpY2UgLSAkbWF0Y2hlZFByaWNlICogJGRpc2NvdW50UmVzdWx0LT5nZXRQZXJjZW50KCk7CiAgICAgICAgICAgICR1c2VkUHJvZHVjdHMgPSAkdXNlZFByb2R1Y3RzLT5tZXJnZSgkbWF0Y2hlZFByb2R1Y3RzKTsKICAgICAgICB9CgogICAgICAgICR0b3RhbFByaWNlICs9ICRwcm9kdWN0Q29sbGVjdGlvbi0+ZGlmZmVyZW5jZSgkdXNlZFByb2R1Y3RzKS0+cmVkdWNlKCRwcmljZXNTdW0sIDApOwoKICAgICAgICByZXR1cm4gJHRvdGFsUHJpY2U7CiAgICB9Cn0KCmludGVyZmFjZSBEaXNjb3VudEludGVyZmFjZQp7CiAgICAvKioKICAgICAqIEBwYXJhbSBQcm9kdWN0Q29sbGVjdGlvbiAkbm90VXNlZFByb2R1Y3RzCiAgICAgKiBAcmV0dXJuIERpc2NvdW50UmVzdWx0CiAgICAgKi8KICAgIHB1YmxpYyBmdW5jdGlvbiBnZXREaXNjb3VudFJlc3VsdChQcm9kdWN0Q29sbGVjdGlvbiAkbm90VXNlZFByb2R1Y3RzKTsKfQoKY2xhc3MgQ29tYmluYXRpb25EaXNjb3VudCBpbXBsZW1lbnRzIERpc2NvdW50SW50ZXJmYWNlCnsKICAgIHByaXZhdGUgJG5hbWVzOwogICAgcHJpdmF0ZSAkcGVyY2VudDsKCiAgICBwdWJsaWMgZnVuY3Rpb24gX19jb25zdHJ1Y3QoYXJyYXkgJG5hbWVzLCAkcGVyY2VudCkKICAgIHsKICAgICAgICAkdGhpcy0+bmFtZXMgPSAkbmFtZXM7CiAgICAgICAgJHRoaXMtPnBlcmNlbnQgPSAkcGVyY2VudDsKICAgIH0KCiAgICAvKioKICAgICAqIEBwYXJhbSBQcm9kdWN0Q29sbGVjdGlvbiAkbm90VXNlZFByb2R1Y3RzCiAgICAgKiBAcmV0dXJuIERpc2NvdW50UmVzdWx0CiAgICAgKi8KICAgIHB1YmxpYyBmdW5jdGlvbiBnZXREaXNjb3VudFJlc3VsdChQcm9kdWN0Q29sbGVjdGlvbiAkbm90VXNlZFByb2R1Y3RzKSB7CiAgICAgICAgJHByb2R1Y3RzVXNlZEluVGhpc0Rpc2NvdW50ID0gbmV3IFByb2R1Y3RDb2xsZWN0aW9uKCk7CgogICAgICAgIGZvcmVhY2ggKCR0aGlzLT5uYW1lcyBhcyAkbmFtZSkgewogICAgICAgICAgICAkbWF0Y2hlZFByb2R1Y3QgPSAkbm90VXNlZFByb2R1Y3RzLT5nZXRGaXJzdEJ5TmFtZSgkbmFtZSk7CiAgICAgICAgICAgIGlmICgkbWF0Y2hlZFByb2R1Y3QpIHsKICAgICAgICAgICAgICAgICRub3RVc2VkUHJvZHVjdHMtPnJlbW92ZVByb2R1Y3QoJG1hdGNoZWRQcm9kdWN0KTsKICAgICAgICAgICAgICAgICRwcm9kdWN0c1VzZWRJblRoaXNEaXNjb3VudC0+YWRkUHJvZHVjdCgkbWF0Y2hlZFByb2R1Y3QpOwogICAgICAgICAgICB9IGVsc2UgewogICAgICAgICAgICAgICAgcmV0dXJuIG5ldyBEaXNjb3VudFJlc3VsdChuZXcgUHJvZHVjdENvbGxlY3Rpb24oKSwgJHRoaXMtPnBlcmNlbnQpOwogICAgICAgICAgICB9CiAgICAgICAgfQoKICAgICAgICByZXR1cm4gbmV3IERpc2NvdW50UmVzdWx0KCRwcm9kdWN0c1VzZWRJblRoaXNEaXNjb3VudCwgJHRoaXMtPnBlcmNlbnQpOwogICAgfQp9CgpjbGFzcyBDb21iaW5hdGlvbk9uZU9mRGlzY291bnQgaW1wbGVtZW50cyBEaXNjb3VudEludGVyZmFjZQp7CiAgICBwcml2YXRlICRvbmVPZk5hbWVzOwogICAgcHJpdmF0ZSAkcGVyY2VudDsKICAgIHByaXZhdGUgJHByb2R1Y3ROYW1lOwoKICAgIHB1YmxpYyBmdW5jdGlvbiBfX2NvbnN0cnVjdCgkcHJvZHVjdE5hbWUsIGFycmF5ICRvbmVPZk5hbWVzLCAkcGVyY2VudCkKICAgIHsKICAgICAgICAkdGhpcy0+b25lT2ZOYW1lcyA9ICRvbmVPZk5hbWVzOwogICAgICAgICR0aGlzLT5wZXJjZW50ID0gJHBlcmNlbnQ7CiAgICAgICAgJHRoaXMtPnByb2R1Y3ROYW1lID0gJHByb2R1Y3ROYW1lOwogICAgfQoKICAgIC8qKgogICAgICogQHBhcmFtIFByb2R1Y3RDb2xsZWN0aW9uICRub3RVc2VkUHJvZHVjdHMKICAgICAqIEByZXR1cm4gRGlzY291bnRSZXN1bHQKICAgICAqLwogICAgcHVibGljIGZ1bmN0aW9uIGdldERpc2NvdW50UmVzdWx0KFByb2R1Y3RDb2xsZWN0aW9uICRub3RVc2VkUHJvZHVjdHMpIHsKICAgICAgICAkcHJvZHVjdCA9ICRub3RVc2VkUHJvZHVjdHMtPmdldEZpcnN0QnlOYW1lKCR0aGlzLT5wcm9kdWN0TmFtZSk7CiAgICAgICAgJGZpcnN0QnlOYW1lID0gJG5vdFVzZWRQcm9kdWN0cy0+Z2V0Rmlyc3RCeU5hbWVzKCR0aGlzLT5vbmVPZk5hbWVzKTsKCiAgICAgICAgaWYgKCRwcm9kdWN0ICYmICRmaXJzdEJ5TmFtZSkgewogICAgICAgICAgICByZXR1cm4gbmV3IERpc2NvdW50UmVzdWx0KG5ldyBQcm9kdWN0Q29sbGVjdGlvbihbJHByb2R1Y3QsICRmaXJzdEJ5TmFtZV0pLCAkdGhpcy0+cGVyY2VudCk7CiAgICAgICAgfQogICAgICAgIHJldHVybiBuZXcgRGlzY291bnRSZXN1bHQobmV3IFByb2R1Y3RDb2xsZWN0aW9uKCksIDApOwogICAgfQp9CgpjbGFzcyBDb3VudERpc2NvdW50IGltcGxlbWVudHMgRGlzY291bnRJbnRlcmZhY2UKewogICAgcHJpdmF0ZSAkZXhjZXB0TmFtZXM7CiAgICBwcml2YXRlICRjb3VudFBlcmNlbnRNYXA7CgogICAgcHVibGljIGZ1bmN0aW9uIF9fY29uc3RydWN0KGFycmF5ICRjb3VudFBlcmNlbnRNYXAsIGFycmF5ICRleGNlcHROYW1lcyA9IFtdKQogICAgewogICAgICAgICR0aGlzLT5jb3VudFBlcmNlbnRNYXAgPSAkY291bnRQZXJjZW50TWFwOwogICAgICAgICR0aGlzLT5leGNlcHROYW1lcyA9ICRleGNlcHROYW1lczsKICAgIH0KCiAgICAvKioKICAgICAqIEBwYXJhbSBQcm9kdWN0Q29sbGVjdGlvbiAkbm90VXNlZFByb2R1Y3RzCiAgICAgKiBAcmV0dXJuIERpc2NvdW50UmVzdWx0CiAgICAgKi8KICAgIHB1YmxpYyBmdW5jdGlvbiBnZXREaXNjb3VudFJlc3VsdChQcm9kdWN0Q29sbGVjdGlvbiAkbm90VXNlZFByb2R1Y3RzKSB7CiAgICAgICAgJG1hdGNoZWRQcm9kdWN0cyA9ICRub3RVc2VkUHJvZHVjdHMtPmdldEFsbEV4Y2VwdE5hbWVzKCR0aGlzLT5leGNlcHROYW1lcyk7CiAgICAgICAgJGNvdW50TWF0Y2hlZCA9ICRtYXRjaGVkUHJvZHVjdHMtPmNvdW50KCk7CgogICAgICAgIGlmIChhcnJheV9rZXlfZXhpc3RzKCRjb3VudE1hdGNoZWQsICR0aGlzLT5jb3VudFBlcmNlbnRNYXApKSB7CiAgICAgICAgICAgIHJldHVybiBuZXcgRGlzY291bnRSZXN1bHQoJG1hdGNoZWRQcm9kdWN0cywgJHRoaXMtPmNvdW50UGVyY2VudE1hcFskY291bnRNYXRjaGVkXSk7CiAgICAgICAgfQogICAgICAgICRtYXhDb3VudCA9IG1heChhcnJheV9rZXlzKCR0aGlzLT5jb3VudFBlcmNlbnRNYXApKTsKICAgICAgICBpZiAoJGNvdW50TWF0Y2hlZCA+ICRtYXhDb3VudCkgewogICAgICAgICAgICByZXR1cm4gbmV3IERpc2NvdW50UmVzdWx0KCRtYXRjaGVkUHJvZHVjdHMsICR0aGlzLT5jb3VudFBlcmNlbnRNYXBbJG1heENvdW50XSk7CiAgICAgICAgfQogICAgICAgIHJldHVybiBuZXcgRGlzY291bnRSZXN1bHQobmV3IFByb2R1Y3RDb2xsZWN0aW9uKCksIDApOwogICAgfQp9CgpjbGFzcyBEaXNjb3VudENvbGxlY3Rpb24gaW1wbGVtZW50cyBJdGVyYXRvckFnZ3JlZ2F0ZQp7CiAgICAvKioKICAgICAqIEB2YXIgRGlzY291bnRJbnRlcmZhY2VbXQogICAgICovCiAgICBwcml2YXRlICRkaXNjb3VudHM7CgogICAgcHVibGljIGZ1bmN0aW9uIF9fY29uc3RydWN0KGFycmF5ICRkaXNjb3VudHMgPSBbXSkKICAgIHsKICAgICAgICAkdGhpcy0+ZGlzY291bnRzID0gW107CiAgICAgICAgZm9yZWFjaCAoJGRpc2NvdW50cyBhcyAkZGlzY291bnQpIHsKICAgICAgICAgICAgJHRoaXMtPmFkZERpc2NvdW50KCRkaXNjb3VudCk7CiAgICAgICAgfQogICAgfQoKICAgIHB1YmxpYyBmdW5jdGlvbiBhZGREaXNjb3VudChEaXNjb3VudEludGVyZmFjZSAkZGlzY291bnQpCiAgICB7CiAgICAgICAgJHRoaXMtPmRpc2NvdW50c1tdID0gJGRpc2NvdW50OwogICAgfQoKICAgIC8qKgogICAgICogQHJldHVybiBBcnJheUl0ZXJhdG9yfERpc2NvdW50SW50ZXJmYWNlW10KICAgICAqLwogICAgcHVibGljIGZ1bmN0aW9uIGdldEl0ZXJhdG9yKCkKICAgIHsKICAgICAgICByZXR1cm4gbmV3IEFycmF5SXRlcmF0b3IoJHRoaXMtPmRpc2NvdW50cyk7CiAgICB9Cn0KCmNsYXNzIFByb2R1Y3RDb2xsZWN0aW9uIGltcGxlbWVudHMgSXRlcmF0b3JBZ2dyZWdhdGUKewogICAgcHJpdmF0ZSAkcHJvZHVjdHM7CgogICAgcHVibGljIGZ1bmN0aW9uIF9fY29uc3RydWN0KGFycmF5ICRwcm9kdWN0cyA9IFtdKQogICAgewogICAgICAgICR0aGlzLT5wcm9kdWN0cyA9IFtdOwogICAgICAgIGZvcmVhY2ggKCRwcm9kdWN0cyBhcyAkcHJvZHVjdCkgewogICAgICAgICAgICAkdGhpcy0+YWRkUHJvZHVjdCgkcHJvZHVjdCk7CiAgICAgICAgfQogICAgfQoKICAgIHB1YmxpYyBmdW5jdGlvbiBhZGRQcm9kdWN0KFByb2R1Y3QgJHByb2R1Y3QpCiAgICB7CiAgICAgICAgJHRoaXMtPnByb2R1Y3RzW10gPSAkcHJvZHVjdDsKICAgIH0KCiAgICAvKioKICAgICAqIEBwYXJhbSBQcm9kdWN0ICRwcm9kdWN0CiAgICAgKiBAcmV0dXJuIFByb2R1Y3R8ZmFsc2UKICAgICAqLwogICAgcHVibGljIGZ1bmN0aW9uIHJlbW92ZVByb2R1Y3QoUHJvZHVjdCAkcHJvZHVjdCkKICAgIHsKICAgICAgICBmb3IgKCRpID0gMDsgJGkgPCBjb3VudCgkdGhpcy0+cHJvZHVjdHMpOyAkaSsrKSB7CiAgICAgICAgICAgIGlmICgkdGhpcy0+cHJvZHVjdHNbJGldID09PSAkcHJvZHVjdCkgewogICAgICAgICAgICAgICAgcmV0dXJuIGFycmF5X3NwbGljZSgkdGhpcy0+cHJvZHVjdHMsICRpLCAxKVswXTsKICAgICAgICAgICAgfQogICAgICAgIH0KICAgICAgICByZXR1cm4gZmFsc2U7CiAgICB9CgogICAgcHVibGljIGZ1bmN0aW9uIHJlZHVjZShjYWxsYWJsZSAkY2FsbGFibGUsICRpbml0aWFsKQogICAgewogICAgICAgIHJldHVybiBhcnJheV9yZWR1Y2UoJHRoaXMtPnByb2R1Y3RzLCAkY2FsbGFibGUsICRpbml0aWFsKTsKICAgIH0KCiAgICAvKioKICAgICAqIEBwYXJhbSBQcm9kdWN0Q29sbGVjdGlvbiAkcHJvZHVjdENvbGxlY3Rpb24KICAgICAqIEByZXR1cm4gUHJvZHVjdENvbGxlY3Rpb24KICAgICAqLwogICAgcHVibGljIGZ1bmN0aW9uIG1lcmdlKFByb2R1Y3RDb2xsZWN0aW9uICRwcm9kdWN0Q29sbGVjdGlvbikKICAgIHsKICAgICAgICByZXR1cm4gbmV3IHNlbGYoYXJyYXlfbWVyZ2UoJHRoaXMtPnByb2R1Y3RzLCAkcHJvZHVjdENvbGxlY3Rpb24tPnRvQXJyYXkoKSkpOwogICAgfQoKICAgIC8qKgogICAgICogQHBhcmFtIFByb2R1Y3RDb2xsZWN0aW9uICRwcm9kdWN0Q29sbGVjdGlvbgogICAgICogQHJldHVybiBQcm9kdWN0Q29sbGVjdGlvbgogICAgICovCiAgICBwdWJsaWMgZnVuY3Rpb24gZGlmZmVyZW5jZShQcm9kdWN0Q29sbGVjdGlvbiAkcHJvZHVjdENvbGxlY3Rpb24pCiAgICB7CiAgICAgICAgJHBjID0gbmV3IHNlbGYoJHRoaXMtPnByb2R1Y3RzKTsKICAgICAgICBmb3JlYWNoICgkcHJvZHVjdENvbGxlY3Rpb24gYXMgJHByb2R1Y3QpIHsKICAgICAgICAgICAgJHBjLT5yZW1vdmVQcm9kdWN0KCRwcm9kdWN0KTsKICAgICAgICB9CiAgICAgICAgcmV0dXJuICRwYzsKICAgIH0KCiAgICAvKioKICAgICAqIEBwYXJhbSAkbmFtZQogICAgICogQHJldHVybiBQcm9kdWN0fG51bGwKICAgICAqLwogICAgcHVibGljIGZ1bmN0aW9uIGdldEZpcnN0QnlOYW1lKCRuYW1lKQogICAgewogICAgICAgIGZvcmVhY2ggKCR0aGlzLT5wcm9kdWN0cyBhcyAkcHJvZHVjdCkgewogICAgICAgICAgICBpZiAoJHByb2R1Y3QtPm5hbWUgPT09ICRuYW1lKSB7CiAgICAgICAgICAgICAgICByZXR1cm4gJHByb2R1Y3Q7CiAgICAgICAgICAgIH0KICAgICAgICB9CiAgICAgICAgcmV0dXJuIG51bGw7CiAgICB9CgogICAgLyoqCiAgICAgKiBAcGFyYW0gYXJyYXkgJG5hbWVzCiAgICAgKiBAcmV0dXJuIFByb2R1Y3R8bnVsbAogICAgICovCiAgICBwdWJsaWMgZnVuY3Rpb24gZ2V0Rmlyc3RCeU5hbWVzKGFycmF5ICRuYW1lcykKICAgIHsKICAgICAgICBmb3JlYWNoICgkbmFtZXMgYXMgJG5hbWUpIHsKICAgICAgICAgICAgJHByb2R1Y3QgPSAkdGhpcy0+Z2V0Rmlyc3RCeU5hbWUoJG5hbWUpOwogICAgICAgICAgICBpZiAoJHByb2R1Y3QpIHsKICAgICAgICAgICAgICAgIHJldHVybiAkcHJvZHVjdDsKICAgICAgICAgICAgfQogICAgICAgIH0KICAgICAgICByZXR1cm4gbnVsbDsKICAgIH0KCiAgICAvKioKICAgICAqIEBwYXJhbSBhcnJheSAkbmFtZXMKICAgICAqIEByZXR1cm4gUHJvZHVjdENvbGxlY3Rpb24KICAgICAqLwogICAgcHVibGljIGZ1bmN0aW9uIGdldEFsbEV4Y2VwdE5hbWVzKGFycmF5ICRuYW1lcykKICAgIHsKICAgICAgICAkYWxsRXhjZXB0TmFtZXMgPSBhcnJheV9maWx0ZXIoCiAgICAgICAgICAgICR0aGlzLT5wcm9kdWN0cywKICAgICAgICAgICAgZnVuY3Rpb24gKFByb2R1Y3QgJHApIHVzZSAoJG5hbWVzKSB7CiAgICAgICAgICAgICAgICByZXR1cm4gIWluX2FycmF5KCRwLT5uYW1lLCAkbmFtZXMsIHRydWUpOwogICAgICAgICAgICB9CiAgICAgICAgKTsKCiAgICAgICAgcmV0dXJuIG5ldyBzZWxmKCRhbGxFeGNlcHROYW1lcyk7CiAgICB9CgogICAgcHVibGljIGZ1bmN0aW9uIHRvQXJyYXkoKQogICAgewogICAgICAgIHJldHVybiAkdGhpcy0+cHJvZHVjdHM7CiAgICB9CgogICAgLyoqCiAgICAgKiBAcGFyYW0gUHJvZHVjdCAkcHJvZHVjdAogICAgICogQHJldHVybiBib29sCiAgICAgKi8KICAgIHB1YmxpYyBmdW5jdGlvbiBjb250YWluc1Byb2R1Y3QoUHJvZHVjdCAkcHJvZHVjdCkKICAgIHsKICAgICAgICByZXR1cm4gaW5fYXJyYXkoJHByb2R1Y3QsICR0aGlzLT5wcm9kdWN0cywgdHJ1ZSk7CiAgICB9CgogICAgLyoqCiAgICAgKiBAcmV0dXJuIGJvb2wKICAgICAqLwogICAgcHVibGljIGZ1bmN0aW9uIGlzRW1wdHkoKQogICAgewogICAgICAgIHJldHVybiBjb3VudCgkdGhpcy0+cHJvZHVjdHMpID09PSAwOwogICAgfQoKICAgIC8qKgogICAgICogQHJldHVybiBpbnQKICAgICAqLwogICAgcHVibGljIGZ1bmN0aW9uIGNvdW50KCkKICAgIHsKICAgICAgICByZXR1cm4gY291bnQoJHRoaXMtPnByb2R1Y3RzKTsKICAgIH0KCiAgICAvKioKICAgICAqIEByZXR1cm4gQXJyYXlJdGVyYXRvcnxQcm9kdWN0W10KICAgICAqLwogICAgcHVibGljIGZ1bmN0aW9uIGdldEl0ZXJhdG9yKCkKICAgIHsKICAgICAgICByZXR1cm4gbmV3IEFycmF5SXRlcmF0b3IoJHRoaXMtPnByb2R1Y3RzKTsKICAgIH0KfQoKY2xhc3MgRGlzY291bnRSZXN1bHQKewogICAgLyoqCiAgICAgKiBAdmFyIFByb2R1Y3RDb2xsZWN0aW9uCiAgICAgKi8KICAgIHByaXZhdGUgJG1hdGNoZWRQcm9kdWN0czsKICAgIHByaXZhdGUgJHBlcmNlbnQ7CgogICAgcHVibGljIGZ1bmN0aW9uIF9fY29uc3RydWN0KFByb2R1Y3RDb2xsZWN0aW9uICRtYXRjaGVkUHJvZHVjdHMsICRwZXJjZW50KQogICAgewogICAgICAgICR0aGlzLT5tYXRjaGVkUHJvZHVjdHMgPSAkbWF0Y2hlZFByb2R1Y3RzOwogICAgICAgICR0aGlzLT5wZXJjZW50ID0gJHBlcmNlbnQ7CiAgICB9CgogICAgLyoqCiAgICAgKiBAcmV0dXJuIFByb2R1Y3RDb2xsZWN0aW9uCiAgICAgKi8KICAgIHB1YmxpYyBmdW5jdGlvbiBnZXRNYXRjaGVkUHJvZHVjdHMoKQogICAgewogICAgICAgIHJldHVybiAkdGhpcy0+bWF0Y2hlZFByb2R1Y3RzOwogICAgfQoKICAgIHB1YmxpYyBmdW5jdGlvbiBnZXRQZXJjZW50KCkKICAgIHsKICAgICAgICByZXR1cm4gJHRoaXMtPnBlcmNlbnQ7CiAgICB9Cn0KCmNsYXNzIFByb2R1Y3QKewogICAgcHVibGljICRuYW1lOwogICAgcHVibGljICRwcmljZTsKCiAgICBwdWJsaWMgZnVuY3Rpb24gX19jb25zdHJ1Y3QoJG5hbWUsICRwcmljZSkKICAgIHsKICAgICAgICAkdGhpcy0+bmFtZSA9ICRuYW1lOwogICAgICAgICR0aGlzLT5wcmljZSA9ICRwcmljZTsKICAgIH0KfQoKJGEgPSBuZXcgUHJvZHVjdCgnYScsIDEwMCk7CiRhMiA9IG5ldyBQcm9kdWN0KCdhJywgMTAwKTsKJGIgPSBuZXcgUHJvZHVjdCgnYicsIDMwMCk7CiRjID0gbmV3IFByb2R1Y3QoJ2MnLCAyMDApOwokZCA9IG5ldyBQcm9kdWN0KCdkJywgMjAwKTsKJGUgPSBuZXcgUHJvZHVjdCgnZScsIDEwMCk7CiRjMiA9IG5ldyBQcm9kdWN0KCdjJywgMTAwKTsKCi8vIFByb2R1Y3RDb2xsZWN0aW9uCiRwYyA9IG5ldyBQcm9kdWN0Q29sbGVjdGlvbihbJGEsICRhMiwgJGIsICRjLCAkZCwgJGVdKTsKYXNzZXJ0KCRwYy0+Y29udGFpbnNQcm9kdWN0KCRhKSk7CmFzc2VydCgkcGMtPmNvbnRhaW5zUHJvZHVjdChuZXcgUHJvZHVjdCgnYScsIDEpKSA9PT0gZmFsc2UpOwphc3NlcnQoJHBjLT5nZXRGaXJzdEJ5TmFtZSgnYScpID09PSAkYSk7CmFzc2VydCgkcGMtPnJlbW92ZVByb2R1Y3QobmV3IFByb2R1Y3QoJ2EnLCAxKSkgPT09IGZhbHNlKTsKYXNzZXJ0KCRwYy0+cmVtb3ZlUHJvZHVjdCgkYSkgPT09ICRhKTsKYXNzZXJ0KCRwYy0+Y29udGFpbnNQcm9kdWN0KCRhKSA9PT0gZmFsc2UpOwphc3NlcnQoJHBjLT5nZXRGaXJzdEJ5TmFtZSgnYScpID09PSAkYTIpOwphc3NlcnQoJHBjLT5tZXJnZShuZXcgUHJvZHVjdENvbGxlY3Rpb24oWyRjMiwgJGFdKSkgPT0gbmV3IFByb2R1Y3RDb2xsZWN0aW9uKFskYTIsICRiLCAkYywgJGQsICRlLCAkYzIsICRhXSkpOwphc3NlcnQoKG5ldyBQcm9kdWN0Q29sbGVjdGlvbihbJGEsICRiLCAkY10pKS0+ZGlmZmVyZW5jZShuZXcgUHJvZHVjdENvbGxlY3Rpb24oWyRhLCAkY10pKSA9PSBuZXcgUHJvZHVjdENvbGxlY3Rpb24oWyRiXSkpOwoKLy8gQ29tYmluYXRpb25EaXNjb3VudAokY2QgPSBuZXcgQ29tYmluYXRpb25EaXNjb3VudChbJ2EnLCAnYiddLCAwLjUpOwokYWxsUHJvZHVjdHMgPSBuZXcgUHJvZHVjdENvbGxlY3Rpb24oWyRhLCAkYiwgJGMsICRkLCAkZSwgJGEyXSk7CiRub3RVc2VkUHJvZHVjdHMxID0gJGFsbFByb2R1Y3RzLT5kaWZmZXJlbmNlKG5ldyBQcm9kdWN0Q29sbGVjdGlvbihbXSkpOwokbm90VXNlZFByb2R1Y3RzMiA9ICRhbGxQcm9kdWN0cy0+ZGlmZmVyZW5jZShuZXcgUHJvZHVjdENvbGxlY3Rpb24oWyRhXSkpOwokbm90VXNlZFByb2R1Y3RzMyA9ICRhbGxQcm9kdWN0cy0+ZGlmZmVyZW5jZShuZXcgUHJvZHVjdENvbGxlY3Rpb24oWyRhMiwgJGFdKSk7CmFzc2VydCgkY2QtPmdldERpc2NvdW50UmVzdWx0KCRub3RVc2VkUHJvZHVjdHMxKS0+Z2V0TWF0Y2hlZFByb2R1Y3RzKCkgPT0gbmV3IFByb2R1Y3RDb2xsZWN0aW9uKFskYSwgJGJdKSk7CmFzc2VydCgkY2QtPmdldERpc2NvdW50UmVzdWx0KCRub3RVc2VkUHJvZHVjdHMyKS0+Z2V0TWF0Y2hlZFByb2R1Y3RzKCkgPT0gbmV3IFByb2R1Y3RDb2xsZWN0aW9uKFskYTIsICRiXSkpOwphc3NlcnQoJGNkLT5nZXREaXNjb3VudFJlc3VsdCgkbm90VXNlZFByb2R1Y3RzMyktPmdldE1hdGNoZWRQcm9kdWN0cygpLT5pc0VtcHR5KCkpOwoKLy8gQ2FsY3VsYXRvciB3aXRoIENvbWJpbmF0aW9uRGlzY291bnQKJGMgPSBuZXcgQ2FsY3VsYXRvcigpOwphc3NlcnQoMjAwID09ICRjLT5jYWxjdWxhdGVUb3RhbFByaWNlKG5ldyBEaXNjb3VudENvbGxlY3Rpb24oWyRjZF0pLCBuZXcgUHJvZHVjdENvbGxlY3Rpb24oWwogICAgbmV3IFByb2R1Y3QoJ2EnLCAxMDApLAogICAgbmV3IFByb2R1Y3QoJ2EnLCAxMDApLApdKSkpOwphc3NlcnQoMTAwID09ICRjLT5jYWxjdWxhdGVUb3RhbFByaWNlKG5ldyBEaXNjb3VudENvbGxlY3Rpb24oWyRjZF0pLCBuZXcgUHJvZHVjdENvbGxlY3Rpb24oWwogICAgbmV3IFByb2R1Y3QoJ2EnLCAxMDApLAogICAgbmV3IFByb2R1Y3QoJ2InLCAxMDApLApdKSkpOwoKJGRpc2NvdW50Q29sbGVjdGlvbiA9IG5ldyBEaXNjb3VudENvbGxlY3Rpb24oWwogICAgbmV3IENvbWJpbmF0aW9uRGlzY291bnQoWydhJywgJ2InLCAnYyddLCAwLjIpLAogICAgbmV3IENvbWJpbmF0aW9uRGlzY291bnQoWydhJywgJ2InLCAnYycsICdkJ10sIDAuOCksCl0pOwokYWJjZENvbGxlY3Rpb24gPSBuZXcgUHJvZHVjdENvbGxlY3Rpb24oWwogICAgbmV3IFByb2R1Y3QoJ2EnLCAxMDApLAogICAgbmV3IFByb2R1Y3QoJ2InLCAxMDApLAogICAgbmV3IFByb2R1Y3QoJ2MnLCAxMDApLAogICAgbmV3IFByb2R1Y3QoJ2QnLCAxMDApLApdKTsKYXNzZXJ0KDEwMCArIDMwMCAtIDMwMCAqIDAuMiA9PSAkYy0+Y2FsY3VsYXRlVG90YWxQcmljZSgkZGlzY291bnRDb2xsZWN0aW9uLCAkYWJjZENvbGxlY3Rpb24pKTsKCiRkaXNjb3VudENvbGxlY3Rpb24gPSBuZXcgRGlzY291bnRDb2xsZWN0aW9uKFsKICAgIG5ldyBDb21iaW5hdGlvbkRpc2NvdW50KFsnYScsICdiJywgJ2MnLCAnZCddLCAwLjgpLAogICAgbmV3IENvbWJpbmF0aW9uRGlzY291bnQoWydhJywgJ2InLCAnYyddLCAwLjIpLApdKTsKYXNzZXJ0KDQwMCAtIDQwMCAqIDAuOCA9PSAkYy0+Y2FsY3VsYXRlVG90YWxQcmljZSgkZGlzY291bnRDb2xsZWN0aW9uLCAkYWJjZENvbGxlY3Rpb24pKTsKCi8vIENvdW50RGlzY291bnQKJGNkID0gbmV3IENvdW50RGlzY291bnQoWwogICAgMSA9PiAwLjMsCiAgICAyID0+IDAuNCwKICAgIDMgPT4gMC41LApdLCBbJ2EnLCAnYiddKTsKCmFzc2VydCgyMDAgLSAyMDAgKiAwLjQgKyAyMDAgPT0gJGMtPmNhbGN1bGF0ZVRvdGFsUHJpY2UobmV3IERpc2NvdW50Q29sbGVjdGlvbihbJGNkXSksICRhYmNkQ29sbGVjdGlvbikpOwphc3NlcnQoMjAwID09ICRjLT5jYWxjdWxhdGVUb3RhbFByaWNlKG5ldyBEaXNjb3VudENvbGxlY3Rpb24oW25ldyBDb3VudERpc2NvdW50KFsyID0+IDAuNV0pXSksICRhYmNkQ29sbGVjdGlvbikpOwoKLy8gQ29tYmluYXRpb25PbmVPZkRpc2NvdW50CiRjb2QgPSBuZXcgQ29tYmluYXRpb25PbmVPZkRpc2NvdW50KCdhJywgWydiJywgJ2MnLCAnZCddLCAwLjUpOwphc3NlcnQoMjAwICsgMTAwID09ICRjLT5jYWxjdWxhdGVUb3RhbFByaWNlKG5ldyBEaXNjb3VudENvbGxlY3Rpb24oWyRjb2RdKSwgJGFiY2RDb2xsZWN0aW9uKSk7CgovLyBNYWluIHRlc3QKJGRpc2NvdW50Q29sbGVjdGlvbiA9IG5ldyBEaXNjb3VudENvbGxlY3Rpb24oWwogICAgbmV3IENvbWJpbmF0aW9uRGlzY291bnQoWydhJywgJ2InXSwgMC4xKSwKICAgIG5ldyBDb21iaW5hdGlvbkRpc2NvdW50KFsnZCcsICdlJ10sIDAuMDUpLAogICAgbmV3IENvbWJpbmF0aW9uRGlzY291bnQoWydmJywgJ2UnLCAnZyddLCAwLjA1KSwKICAgIG5ldyBDb21iaW5hdGlvbk9uZU9mRGlzY291bnQoJ2EnLCBbJ2snLCAnaicsICdtJ10sIDAuMDUpLAogICAgbmV3IENvdW50RGlzY291bnQoWzMgPT4gMC4wNSwgNCA9PiAwLjAxLCA1ID0+IDAuMDJdLCBbJ2EnLCAnYyddKSwKXSk7CiRwcm9kdWN0Q29sbGVjdGlvbiA9IG5ldyBQcm9kdWN0Q29sbGVjdGlvbihbCiAgICBuZXcgUHJvZHVjdCgnYScsIDEwMCksCiAgICBuZXcgUHJvZHVjdCgnYicsIDEwMCksCiAgICBuZXcgUHJvZHVjdCgnYycsIDEwMCksCiAgICBuZXcgUHJvZHVjdCgnZCcsIDEwMCksCiAgICBuZXcgUHJvZHVjdCgnZScsIDEwMCksCiAgICBuZXcgUHJvZHVjdCgnZicsIDEwMCksCiAgICBuZXcgUHJvZHVjdCgnZycsIDEwMCksCiAgICBuZXcgUHJvZHVjdCgnaCcsIDEwMCksCiAgICBuZXcgUHJvZHVjdCgnaScsIDEwMCksCiAgICBuZXcgUHJvZHVjdCgnaicsIDEwMCksCiAgICBuZXcgUHJvZHVjdCgnYScsIDEwMCksCl0pOwoKJGFuc3dlciA9ICgyMDAgLSAyMDAgKiAwLjEpICsgKDIwMCAtIDIwMCAqIDAuMDUpICsgKDIwMCAtIDIwMCAqIDAuMDUpICsgKDQwMCAtIDQwMCAqIDAuMDEpICsgMTAwOwphc3NlcnQoJGFuc3dlciA9PSAkYy0+Y2FsY3VsYXRlVG90YWxQcmljZSgkZGlzY291bnRDb2xsZWN0aW9uLCAkcHJvZHVjdENvbGxlY3Rpb24pKTs=