class ProductServiceFacadeImpl implements ProductServiceFacade {

    @Override
    public PriceModel getCartItemsTotal(final ShoppingCart cart) {

        final String currency = cart.getCurrencyCode();

        if (cart.getShoppingContext().isHidePrices()) {
            return getNullProductPriceModel(currency);
        }

        final BigDecimal list = cart.getTotal().getListSubTotal();
        final BigDecimal sale = cart.getTotal().getSubTotal();

        final boolean showTax = cart.getShoppingContext().isTaxInfoEnabled();
        final boolean showTaxNet = showTax && cart.getShoppingContext().isTaxInfoUseNet();
        final boolean showTaxAmount = showTax && cart.getShoppingContext().isTaxInfoShowAmount();

        if (showTax) {

            final BigDecimal totalInclTax = cart.getTotal().getSubTotalAmount();

            if (MoneyUtils.isPositive(totalInclTax)) {
                final BigDecimal totalTax = cart.getTotal().getSubTotalTax();
                final BigDecimal net = totalInclTax.subtract(totalTax);
                final BigDecimal gross = totalInclTax;

                final BigDecimal totalAdjusted = showTaxNet ? net : gross;

                final Set<String> taxes = new TreeSet<>(); // sorts and de-duplicate
                final Set<BigDecimal> rates = new TreeSet<>();
                for (final CartItem item : cart.getCartItemList()) {
                    if (StringUtils.isNotBlank(item.getTaxCode())) {
                        taxes.add(item.getTaxCode());
                    }
                    rates.add(item.getTaxRate());
                }

                final BigDecimal taxRate;
                if (MoneyUtils.isPositive(totalTax) && rates.size() > 1) {
                    // mixed rates in cart we use average with round up so that tax is not reduced by rounding
                    taxRate = totalTax.multiply(MoneyUtils.HUNDRED)
                            .divide(net, 1, BigDecimal.ROUND_HALF_UP)
                                .setScale(Constants.MONEY_SCALE, BigDecimal.ROUND_HALF_UP);
                } else {
                    // single rate for all items, use it to prevent rounding errors
                    taxRate = rates.iterator().next();
                }

                final String tax = StringUtils.join(taxes, ',');
                final boolean exclusiveTax = totalInclTax.compareTo(sale) > 0;

                if (MoneyUtils.isFirstBiggerThanSecond(list, sale)) {
                    // if we have discounts

                    final MoneyUtils.Money listMoney = MoneyUtils.getMoney(list, taxRate, !exclusiveTax);
                    final BigDecimal listAdjusted = showTaxNet ? listMoney.getNet() : listMoney.getGross();

                    return new PriceModelImpl(
                            CART_ITEMS_TOTAL_REF,
                            currency,
                            BigDecimal.ONE,
                            false,
                            false,
                            listAdjusted, totalAdjusted,
                            showTax, showTaxNet, showTaxAmount,
                            tax,
                            taxRate,
                            exclusiveTax,
                            totalTax
                    );

                }
                // no discounts
                return new PriceModelImpl(
                        CART_ITEMS_TOTAL_REF,
                        currency,
                        BigDecimal.ONE,
                        false,
                        false,
                        totalAdjusted, null,
                        showTax, showTaxNet, showTaxAmount,
                        tax,
                        taxRate,
                        exclusiveTax,
                        totalTax
                );

            }
        }

        // standard "as is" prices

        if (MoneyUtils.isPositive(sale)
                && MoneyUtils.isFirstBiggerThanSecond(list, sale)) {
            // if we have discounts
            return new PriceModelImpl(
                    CART_ITEMS_TOTAL_REF,
                    currency,
                    BigDecimal.ONE,
                    false,
                    false,
                    list, sale
            );

        }
        // no discounts
        return new PriceModelImpl(
                CART_ITEMS_TOTAL_REF,
                currency,
                BigDecimal.ONE,
                false,
                false,
                sale, null
        );

    }

    public  ProductServiceFacadeImpl(final ProductService productService,
                                    final ProductSkuService productSkuService,
                                    final ProductAssociationService productAssociationService,
                                    final SearchQueryFactory searchQueryFactory,
                                    final ProductAvailabilityStrategy productAvailabilityStrategy,
                                    final ProductQuantityStrategy productQuantityStrategy,
                                    final PricingPolicyProvider pricingPolicyProvider,
                                    final PriceResolver priceResolver,
                                    final ShoppingCartCalculator shoppingCartCalculator,
                                    final PromotionService promotionService,
                                    final CategoryServiceFacade categoryServiceFacade,
                                    final ShopService shopService,
                                    final BrandService brandService);

}