<commit_before>/*
 * Licensed under MIT (https://github.com/ligoj/ligoj/blob/master/LICENSE)
 */
package org.ligoj.app.plugin.prov;

import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import javax.transaction.Transactional;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

import org.apache.commons.lang3.BooleanUtils;
import org.hibernate.Hibernate;
import org.ligoj.app.plugin.prov.dao.ProvBudgetRepository;
import org.ligoj.app.plugin.prov.model.AbstractInstanceType;
import org.ligoj.app.plugin.prov.model.AbstractQuoteVm;
import org.ligoj.app.plugin.prov.model.AbstractTermPriceVm;
import org.ligoj.app.plugin.prov.model.ProvBudget;
import org.ligoj.app.plugin.prov.model.ProvQuote;
import org.ligoj.app.plugin.prov.model.ProvQuoteContainer;
import org.ligoj.app.plugin.prov.model.ProvQuoteDatabase;
import org.ligoj.app.plugin.prov.model.ProvQuoteFunction;
import org.ligoj.app.plugin.prov.model.ProvQuoteInstance;
import org.ligoj.app.plugin.prov.model.ResourceScope;
import org.ligoj.app.plugin.prov.model.ResourceType;
import org.ligoj.bootstrap.resource.system.configuration.ConfigurationResource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import net.jnellis.binpack.LinearBin;
import net.jnellis.binpack.LinearBinPacker;

/**
 * Budget part of provisioning.
 */
@Service
@Path(ProvResource.SERVICE_URL + "/{subscription:\\d+}/budget")
@Produces(MediaType.APPLICATION_JSON)
@Transactional
@Slf4j
public class ProvBudgetResource extends AbstractMultiScopedResource<ProvBudget, ProvBudgetRepository, BudgetEditionVo> {

	private static final String CODE = " (code=";

	@Autowired
	@Getter
	private ProvBudgetRepository repository;

	@Autowired
	private ConfigurationResource configuration;

	/**
	 * Create a budget initiated without any cost.
	 */
	public ProvBudgetResource() {
		super(ResourceScope::getBudget, ResourceScope::setBudget, ProvBudget::new);
	}

	@Override
	protected UpdatedCost saveOrUpdate(final ProvBudget entity, final BudgetEditionVo vo) {
		// Check the associations and copy attributes to the entity
		entity.setName(vo.getName());
		entity.setInitialCost(vo.getInitialCost());

		// Fetch the budgets of this quotes
		final var quote = entity.getConfiguration();
		Hibernate.initialize(quote.getBudgets());

		// Prepare the updated cost of updated instances
		final var relatedCosts = Collections
				.synchronizedMap(new EnumMap<ResourceType, Map<Integer, FloatingCost>>(ResourceType.class));
		// Prevent useless computation, check the relations
		if (entity.getId() != null) {
			// This is an update, update the cost of all related instances
			lean(entity, relatedCosts);
		}

		repository.saveAndFlush(entity);

		// Update accordingly the support costs
		final var cost = new UpdatedCost(entity.getId());
		cost.setRelated(relatedCosts);

		final var updateCost = resource.refreshSupportCost(cost, quote);
		log.info("Total2 monthly cost: {}", updateCost.getTotal().getMin());
		log.info("Total2 initial cost: {}", updateCost.getTotal().getInitial());
		return updateCost;
	}

	/**
	 * Refresh the whole quote budgets.
	 *
	 * @param quote The quote owning the related budget.
	 * @param costs The updated costs and resources.
	 */
	public void lean(final ProvQuote quote, final Map<ResourceType, Map<Integer, FloatingCost>> costs) {
		final var instances = qiRepository.findAll(quote);
		final var databases = qbRepository.findAll(quote);
		final var containers = qcRepository.findAll(quote);
		final var functions = qfRepository.findAll(quote);
		Hibernate.initialize(quote.getUsages());
		Hibernate.initialize(quote.getBudgets());
		lean(quote, instances, databases, containers, functions, costs);

		// Reset the orphan budgets
		final var usedBudgets = Stream.concat(instances.stream(), databases.stream())
				.map(AbstractQuoteVm::getResolvedBudget).filter(Objects::nonNull).distinct().map(ProvBudget::getId)
				.collect(Collectors.toSet());
		repository.findAll(quote).stream().filter(b -> !usedBudgets.contains(b.getId()))
				.forEach(b -> b.setRequiredInitialCost(0d));
	}

	/**
	 * Detect the related budgets having an initial cost and involved in the given instances/databases collections, lean
	 * them. A refresh is also applied to all resources related to these budgets. This means that some resources not
	 * included in the initial set may be refreshed.
	 *
	 * @param quote      The quote owning the related budget.
	 * @param instances  The instances implied in the current change.
	 * @param databases  The databases implied in the current change.
	 * @param containers The containers implied in the current change.
	 * @param functions  The functions implied in the current change.
	 * @param costs      The updated costs and resources.
	 */
	public void lean(final ProvQuote quote, final List<ProvQuoteInstance> instances,
			final List<ProvQuoteDatabase> databases, final List<ProvQuoteContainer> containers,
			final List<ProvQuoteFunction> functions, final Map<ResourceType, Map<Integer, FloatingCost>> costs) {
		synchronized (quote) {
			// Lean all relevant budgets
			final var budgets = Stream
					.concat(instances.stream(), Stream.concat(databases.stream(), containers.stream()))
					.map(AbstractQuoteVm::getResolvedBudget).filter(Objects::nonNull)
					.filter(b -> b.getInitialCost() > 0).distinct().collect(Collectors.toList());
			budgets.forEach(b -> lean(b, costs));

			// Refresh also all remaining resources unrelated to the updated budgets
			refreshNoBudget(instances, ResourceType.INSTANCE, costs, qiResource);
			refreshNoBudget(databases, ResourceType.DATABASE, costs, qbResource);
			refreshNoBudget(containers, ResourceType.CONTAINER, costs, qcResource);
			refreshNoBudget(functions, ResourceType.FUNCTION, costs, qfResource);
		}
	}

	private <T extends AbstractInstanceType, P extends AbstractTermPriceVm<T>, C extends AbstractQuoteVm<P>> void refreshNoBudget(
			final List<C> instances, final ResourceType type, final Map<ResourceType, Map<Integer, FloatingCost>> costs,
			final AbstractProvQuoteVmResource<T, P, C, ?, ?, ?> resource) {
		this.resource.newStream(instances)
				.filter(i -> Optional.ofNullable(i.getResolvedBudget()).map(ProvBudget::getInitialCost).orElse(0d) == 0)
				.forEach(i -> costs.computeIfAbsent(type, k -> new HashMap<>()).put(i.getId(),
						resource.addCost(i, resource::refresh)));
	}

	/**
	 * Detect the related resources to the given budget and refresh them according to the budget constraints.
	 *
	 * @param budget The budget to lean.
	 * @param costs  The updated costs and resources.
	 */
	public void lean(final ProvBudget budget, final Map<ResourceType, Map<Integer, FloatingCost>> costs) {
		if (budget == null) {<commit_msg>Add serverless support to budget<commit_after>/*
 * Licensed under MIT (https://github.com/ligoj/ligoj/blob/master/LICENSE)
 */
package org.ligoj.app.plugin.prov;

import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import javax.transaction.Transactional;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

import org.apache.commons.lang3.BooleanUtils;
import org.hibernate.Hibernate;
import org.ligoj.app.plugin.prov.dao.ProvBudgetRepository;
import org.ligoj.app.plugin.prov.model.AbstractInstanceType;
import org.ligoj.app.plugin.prov.model.AbstractQuoteVm;
import org.ligoj.app.plugin.prov.model.AbstractTermPriceVm;
import org.ligoj.app.plugin.prov.model.ProvBudget;
import org.ligoj.app.plugin.prov.model.ProvQuote;
import org.ligoj.app.plugin.prov.model.ProvQuoteContainer;
import org.ligoj.app.plugin.prov.model.ProvQuoteDatabase;
import org.ligoj.app.plugin.prov.model.ProvQuoteFunction;
import org.ligoj.app.plugin.prov.model.ProvQuoteInstance;
import org.ligoj.app.plugin.prov.model.ResourceScope;
import org.ligoj.app.plugin.prov.model.ResourceType;
import org.ligoj.bootstrap.resource.system.configuration.ConfigurationResource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import net.jnellis.binpack.LinearBin;
import net.jnellis.binpack.LinearBinPacker;

/**
 * Budget part of provisioning.
 */
@Service
@Path(ProvResource.SERVICE_URL + "/{subscription:\\d+}/budget")
@Produces(MediaType.APPLICATION_JSON)
@Transactional
@Slf4j
public class ProvBudgetResource extends AbstractMultiScopedResource<ProvBudget, ProvBudgetRepository, BudgetEditionVo> {

	private static final String CODE = " (code=";

	@Autowired
	@Getter
	private ProvBudgetRepository repository;

	@Autowired
	private ConfigurationResource configuration;

	/**
	 * Create a budget initiated without any cost.
	 */
	public ProvBudgetResource() {
		super(ResourceScope::getBudget, ResourceScope::setBudget, ProvBudget::new);
	}

	@Override
	protected UpdatedCost saveOrUpdate(final ProvBudget entity, final BudgetEditionVo vo) {
		// Check the associations and copy attributes to the entity
		entity.setName(vo.getName());
		entity.setInitialCost(vo.getInitialCost());

		// Fetch the budgets of this quotes
		final var quote = entity.getConfiguration();
		Hibernate.initialize(quote.getBudgets());

		// Prepare the updated cost of updated instances
		final var relatedCosts = Collections
				.synchronizedMap(new EnumMap<ResourceType, Map<Integer, FloatingCost>>(ResourceType.class));
		// Prevent useless computation, check the relations
		if (entity.getId() != null) {
			// This is an update, update the cost of all related instances
			lean(entity, relatedCosts);
		}

		repository.saveAndFlush(entity);

		// Update accordingly the support costs
		final var cost = new UpdatedCost(entity.getId());
		cost.setRelated(relatedCosts);

		final var updateCost = resource.refreshSupportCost(cost, quote);
		log.info("Total2 monthly cost: {}", updateCost.getTotal().getMin());
		log.info("Total2 initial cost: {}", updateCost.getTotal().getInitial());
		return updateCost;
	}

	/**
	 * Refresh the whole quote budgets.
	 *
	 * @param quote The quote owning the related budget.
	 * @param costs The updated costs and resources.
	 */
	public void lean(final ProvQuote quote, final Map<ResourceType, Map<Integer, FloatingCost>> costs) {
		final var instances = qiRepository.findAll(quote);
		final var databases = qbRepository.findAll(quote);
		final var containers = qcRepository.findAll(quote);
		final var functions = qfRepository.findAll(quote);
		Hibernate.initialize(quote.getUsages());
		Hibernate.initialize(quote.getBudgets());
		lean(quote, instances, databases, containers, functions, costs);

		// Reset the orphan budgets
		final var usedBudgets = Stream.concat(instances.stream(), databases.stream())
				.map(AbstractQuoteVm::getResolvedBudget).filter(Objects::nonNull).distinct().map(ProvBudget::getId)
				.collect(Collectors.toSet());
		repository.findAll(quote).stream().filter(b -> !usedBudgets.contains(b.getId()))
				.forEach(b -> b.setRequiredInitialCost(0d));
	}

	/**
	 * Detect the related budgets having an initial cost and involved in the given instances/databases collections, lean
	 * them. A refresh is also applied to all resources related to these budgets. This means that some resources not
	 * included in the initial set may be refreshed.
	 *
	 * @param quote      The quote owning the related budget.
	 * @param instances  The instances implied in the current change.
	 * @param databases  The databases implied in the current change.
	 * @param containers The containers implied in the current change.
	 * @param functions  The functions implied in the current change.
	 * @param costs      The updated costs and resources.
	 */
	public void lean(final ProvQuote quote, final List<ProvQuoteInstance> instances,
			final List<ProvQuoteDatabase> databases, final List<ProvQuoteContainer> containers,
			final List<ProvQuoteFunction> functions, final Map<ResourceType, Map<Integer, FloatingCost>> costs) {
		synchronized (quote) {
			// Lean all relevant budgets
			final var budgets = Stream
					.concat(instances.stream(),
							Stream.concat(databases.stream(), Stream.concat(containers.stream(), functions.stream())))
					.map(AbstractQuoteVm::getResolvedBudget).filter(Objects::nonNull)
					.filter(b -> b.getInitialCost() > 0).distinct().collect(Collectors.toList());
			budgets.forEach(b -> lean(b, costs));

			// Refresh also all remaining resources unrelated to the updated budgets
			refreshNoBudget(instances, ResourceType.INSTANCE, costs, qiResource);
			refreshNoBudget(databases, ResourceType.DATABASE, costs, qbResource);
			refreshNoBudget(containers, ResourceType.CONTAINER, costs, qcResource);
			refreshNoBudget(functions, ResourceType.FUNCTION, costs, qfResource);
		}
	}

	private <T extends AbstractInstanceType, P extends AbstractTermPriceVm<T>, C extends AbstractQuoteVm<P>> void refreshNoBudget(
			final List<C> instances, final ResourceType type, final Map<ResourceType, Map<Integer, FloatingCost>> costs,
			final AbstractProvQuoteVmResource<T, P, C, ?, ?, ?> resource) {
		this.resource.newStream(instances)
				.filter(i -> Optional.ofNullable(i.getResolvedBudget()).map(ProvBudget::getInitialCost).orElse(0d) == 0)
				.forEach(i -> costs.computeIfAbsent(type, k -> new HashMap<>()).put(i.getId(),
						resource.addCost(i, resource::refresh)));
	}

	/**
	 * Detect the related resources to the given budget and refresh them according to the budget constraints.
	 *
	 * @param budget The budget to lean.
	 * @param costs  The updated costs and resources.
	 */
	public void lean(final ProvBudget budget, final Map<ResourceType, Map<Integer, FloatingCost>> costs) {
		if (budget == null) {
{'fraction_non_alphanumeric': 0.11106509284185821, 'fraction_numerical': 0.0013805480775868019, 'mean_word_length': 5.10021052631579, 'pattern_counts': {'":': 0, '<': 45, '<?xml version=': 0, '>': 61, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3139294', 'n_tokens_mistral': 4570, 'n_tokens_neox': 4187, 'n_words': 1081}