<commit_before>package core;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import org.apache.commons.lang3.tuple.Pair;


public class Marche {
	private volatile boolean ouvert;
	private volatile boolean fini;
	private long debut;
	private Map<Action, Set<Ordre>> liste_achats;
	private Map<Action, Set<Ordre>> liste_ventes;
	private List<Joueur> liste_joueurs;
	private Set<Integer> liste_id_ordres;
	private Map<Action, TreeSet<Echange>> historiques;
	private final ReadWriteLock mutex_ordre = new ReentrantReadWriteLock();
	private final Lock mutex_ordre_read = mutex_ordre.readLock();
	private final Lock mutex_ordre_write = mutex_ordre.writeLock();
	private Thread timer = null;
	private List<Operation> liste_Operations;
	private long echange_unique = 0;
	private long ordre_unique = 0;
	private final int initial_euros;
	
	public Marche(int modeBanque) {
		ouvert = false;
		fini = false;
		liste_achats = new HashMap<>();
		liste_ventes = new HashMap<>();
		historiques = new HashMap<>();
		liste_Operations = new LinkedList<Operation>();
		for (Action a : Action.values()) {
			liste_achats.put(a, new TreeSet<Ordre>());
			liste_ventes.put(a, new TreeSet<Ordre>());
			historiques.put(a, new TreeSet<Echange>());
		}
		liste_joueurs = new LinkedList<>();
		liste_id_ordres = new TreeSet<>();
		
		Random r = new Random();
		int pow = Config.getInstance().POW_EURO_INIT_MIN + 
				r.nextInt(Config.getInstance().POW_EURO_INIT_MAX - Config.getInstance().POW_EURO_INIT_MIN + 1);
		initial_euros = (int) Math.pow(10, pow);
		
		if(Config.getInstance().BANQUE){
			if((modeBanque==1 && r.nextBoolean()) || modeBanque==2){
				Joueur banque = creer_joueur("banque", "banque.root");
				banque.setSolde_euros(Integer.MAX_VALUE);
				int max_action_en_jeu = Config.getInstance().SOLDE_ACTIONS_INIT*Action.values().length*100;//100 joueurs
				float prix_vente = (((float)initial_euros*0.1f)/((float)Config.getInstance().SOLDE_ACTIONS_INIT*2));
				prix_vente /= 2.5f;
				float prix_achat = ((float)initial_euros*0.1f)/((float)Config.getInstance().SOLDE_ACTIONS_INIT*0.1f);
				prix_achat *= 2.5f;
				//System.out.println(initial_euros+": "+prix_vente+" "+prix_achat);
				for(Action a : Action.values()){
					banque.getSolde_actions().put(a, Integer.MAX_VALUE);
					//prix plus à jour
					achat(banque, a, prix_vente, max_action_en_jeu);
					vend(banque, a, prix_achat, max_action_en_jeu);
				}
			}
		}
	}

	public boolean est_ouvert() {
		return ouvert;
	}

	public void commence() {
		ouvert = true;
		debut = System.currentTimeMillis();

		timer = new Thread() {
			@Override
			public void run() {
				try {
					Thread.sleep(1000 * 60 * Config.getInstance().TEMPS_PARTIE);
				} catch (InterruptedException e) {
					//might be interrupted if creator leaves
					//don't print error in this case
				}
				fini = true;
				try {
					Thread.sleep(1000); // sleep 1 s to be sure fini is taking into account
				} catch (InterruptedException e) {
					//might be interrupted if creator leaves
					//don't print error in this case
				}
				//fermer tous les ordres
				for(Joueur joueur : liste_joueurs){
					List<Integer> ordre_supprimer = new LinkedList<>();
					for(Pair<Integer, Ordre> t : joueur.getOperationsOuvertes()){
						ordre_supprimer.add(t.getLeft());
					}
					for(Integer i : ordre_supprimer){
						annuler(joueur, i);
					}
				}
			}
		};
		timer.start();
	}

	public boolean est_fini() {
		return fini;
	}

	// synchronized pour les rares appels de gestion de joueur
	public synchronized Joueur creer_joueur(String nom, String nom_complet) {
		Joueur j = new Joueur(nom, initial_euros, nom_complet);
		liste_joueurs.add(j);
		return j;
	}

	public synchronized boolean nom_possible(String nom) {
		for (Joueur j : liste_joueurs)
			if (j.getNom().equalsIgnoreCase(nom))
				return false;
		return true;
	}

	//synchronized est moins efficace car il bloque les lectures concurrentes
	public String getListeAchatsString(Action a, int nbMaxAchats) {
		mutex_ordre_read.lock();
		String r;
		if(nbMaxAchats<=0 || nbMaxAchats>=liste_achats.get(a).size())
			r = String.valueOf(liste_achats.get(a));
		else{
			LinkedList<Ordre> list = new LinkedList<Ordre>();
			final Iterator<Ordre> i = liste_achats.get(a).iterator();
			for (int j=0; j<nbMaxAchats && i.hasNext();j++)
				list.add(i.next());
			r=String.valueOf(list);
		}
		mutex_ordre_read.unlock();
		return r;
	}

	public String getListeVentesString(Action a, int nbMaxVentes) {
		mutex_ordre_read.lock();
		String r;
		if(nbMaxVentes<=0 || nbMaxVentes>liste_ventes.get(a).size())
			r = String.valueOf(liste_ventes.get(a));
		else{
			LinkedList<Ordre> list = new LinkedList<Ordre>();
			final Iterator<Ordre> i = liste_ventes.get(a).iterator();
			for (int j=0; j<nbMaxVentes && i.hasNext();j++)
				list.add(i.next());
			r=String.valueOf(list);
		}
		mutex_ordre_read.unlock();
		return r;
	}
	
	/**
	 * @param a : le nom de l'action
	 * @param n : le numéro de liste à partir duquel il faut envoyer les éléments historiques du serveur au client
	 * @return  : retourne une liste chainée contenant les éléments de 'historiques' voulus
	 */
	public LinkedList<Echange> getHistoriqueEchanges(Action a, int n) {
		mutex_ordre_read.lock();
		int tailleH=historiques.get(a).size(); //Pour calculer une seule fois la taille de la liste
		LinkedList<Echange> list = new LinkedList<Echange>();//on instancie la liste chainée que l'on va remplir
		
		//on crée un iterateur parcourant 'historiques' dans le sens décroissant
		final Iterator<Echange> i = historiques.get(a).descendingIterator();
		for (int j=0; j<tailleH-n && i.hasNext();j++)
			//on ajoute dans la liste chainée les éléments au sens décroissants de 'historiques'
			//au début de la liste chainée à chaque fois pour que les éléments apparaissent dans le bon sens
			list.addFirst(i.next());
		
		mutex_ordre_read.unlock();
		//on retourne la liste chainée
		return list;
	}

	public int achat(Joueur joueur_achat, Action a, float prix_achat, int volume_achat) {
		if (volume_achat <= 0.0)
			return -5;
		if (prix_achat <= 0.0)
			return -6;

		volume_achat= (int)((int)(volume_achat*prix_achat)/prix_achat);
		if(volume_achat==0)
			return -8;
		
		mutex_ordre_write.lock();
		int argent_joueur= joueur_achat.getSolde_euros();
		int argent_engage = (int) (volume_achat * prix_achat);
		if (argent_joueur < argent_engage) {
			mutex_ordre_write.unlock();
			return -7;
		}

		if (!joueur_achat.getNom().equals("banque"))
			liste_Operations.add(new Operation("Achat",joueur_achat.getNom(),a.toString(),prix_achat,volume_achat));
		
		Iterator<Ordre> it = liste_ventes.get(a).iterator();

		while (it.hasNext() && volume_achat > 0) {
			Ordre vente = it.next();
			Joueur joueur_vente = vente.getJoueur();

			if (prix_achat >= vente.prix) {
				Echange e = new Echange(joueur_vente, joueur_achat, vente.prix, Math.min(vente.volume, volume_achat), echange_unique++);
				historiques.get(a).add(e);

				// l'achat est complete
				if (volume_achat <= vente.volume) {
					int volume_vendu = volume_achat;

					joueur_vente.setSolde_euros(joueur_vente.getSolde_euros() + (int) (vente.prix * volume_vendu));
					joueur_achat.setSolde_euros(joueur_achat.getSolde_euros() - (int) (Math.ceil(vente.prix * volume_vendu)));
					joueur_achat.getSolde_actions().put(a, joueur_achat.getSolde_actions().get(a) + volume_vendu);
					vente.setVolume(vente.getVolume() - volume_vendu);
					
					//ordre restant vide
					if(vente.getVolume() == 0){
						Integer id_vente = vente.getId_ordre();
						joueur_vente.retirerOperation(id_vente);
						it.remove();
					}

					mutex_ordre_write.unlock();
					return 0;
				}
				// volume_achat > vente.volume

				int volume_vendu = vente.volume;

				joueur_vente.setSolde_euros(joueur_vente.getSolde_euros() + (int) (vente.prix * volume_vendu));
				joueur_achat.setSolde_euros(joueur_achat.getSolde_euros() - (int) (Math.ceil(vente.prix * volume_vendu)));
				joueur_achat.getSolde_actions().put(a, joueur_achat.getSolde_actions().get(a) + volume_vendu);
				vente.setVolume(vente.getVolume() - volume_vendu);

				// remove
				Integer id_vente = vente.getId_ordre();
				joueur_vente.retirerOperation(id_vente);
				it.remove();

				volume_achat -= volume_vendu;
			} else
				break;
		}

		// provisionne le reste
		joueur_achat.setSolde_euros(joueur_achat.getSolde_euros() - (int) (Math.ceil(prix_achat * volume_achat)));

		int id = creer_id_ordre();
		Ordre achat = new Achat(id, a, prix_achat, volume_achat, joueur_achat, ordre_unique++);
		liste_achats.get(a).add(achat);
		joueur_achat.getOperationsOuvertes().add(Pair.of(id, achat));
		mutex_ordre_write.unlock();
		return id;
	}

	public int vend(Joueur joueur_vente, Action a, float prix_vente, int volume_vente) {
		if (volume_vente <= 0.0)
			return -8;
		if (prix_vente <= 0.0)
			return -9;
		
		volume_vente= (int)((int)(volume_vente * prix_vente)/prix_vente);
		if(volume_vente==0)
			return -11;

		mutex_ordre_write.lock();
		int volume_joueur = joueur_vente.getSolde_actions().get(a);
		if (volume_joueur < volume_vente) {
			mutex_ordre_write.unlock();
			return -10;
		}

		if (!joueur_vente.getNom().equals("banque"))
			liste_Operations.add(new Operation("Vente",joueur_vente.getNom(),a.toString(),prix_vente,volume_vente));
		
		joueur_vente.getSolde_actions().put(a, volume_joueur - volume_vente);
		Iterator<Ordre> it = liste_achats.get(a).iterator();

		while (it.hasNext() && volume_vente > 0) {
			Achat achat = (Achat) it.next();
			Joueur joueur_achat = achat.getJoueur();

			if (prix_vente <= achat.prix) {
				Echange e = new Echange(joueur_vente, joueur_achat, achat.prix, Math.min(achat.volume, volume_vente), echange_unique++);
				historiques.get(a).add(e);

				// la vente est complete<commit_msg>Revert "Résolution bug perte d'argent #29"<commit_after>package core;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import org.apache.commons.lang3.tuple.Pair;


public class Marche {
	private volatile boolean ouvert;
	private volatile boolean fini;
	private long debut;
	private Map<Action, Set<Ordre>> liste_achats;
	private Map<Action, Set<Ordre>> liste_ventes;
	private List<Joueur> liste_joueurs;
	private Set<Integer> liste_id_ordres;
	private Map<Action, TreeSet<Echange>> historiques;
	private final ReadWriteLock mutex_ordre = new ReentrantReadWriteLock();
	private final Lock mutex_ordre_read = mutex_ordre.readLock();
	private final Lock mutex_ordre_write = mutex_ordre.writeLock();
	private Thread timer = null;
	private List<Operation> liste_Operations;
	private long echange_unique = 0;
	private long ordre_unique = 0;
	private final int initial_euros;
	
	public Marche(int modeBanque) {
		ouvert = false;
		fini = false;
		liste_achats = new HashMap<>();
		liste_ventes = new HashMap<>();
		historiques = new HashMap<>();
		liste_Operations = new LinkedList<Operation>();
		for (Action a : Action.values()) {
			liste_achats.put(a, new TreeSet<Ordre>());
			liste_ventes.put(a, new TreeSet<Ordre>());
			historiques.put(a, new TreeSet<Echange>());
		}
		liste_joueurs = new LinkedList<>();
		liste_id_ordres = new TreeSet<>();
		
		Random r = new Random();
		int pow = Config.getInstance().POW_EURO_INIT_MIN + 
				r.nextInt(Config.getInstance().POW_EURO_INIT_MAX - Config.getInstance().POW_EURO_INIT_MIN + 1);
		initial_euros = (int) Math.pow(10, pow);
		
		if(Config.getInstance().BANQUE){
			if((modeBanque==1 && r.nextBoolean()) || modeBanque==2){
				Joueur banque = creer_joueur("banque", "banque.root");
				banque.setSolde_euros(Integer.MAX_VALUE);
				int max_action_en_jeu = Config.getInstance().SOLDE_ACTIONS_INIT*Action.values().length*100;//100 joueurs
				float prix_vente = (((float)initial_euros*0.1f)/((float)Config.getInstance().SOLDE_ACTIONS_INIT*2));
				prix_vente /= 2.5f;
				float prix_achat = ((float)initial_euros*0.1f)/((float)Config.getInstance().SOLDE_ACTIONS_INIT*0.1f);
				prix_achat *= 2.5f;
				//System.out.println(initial_euros+": "+prix_vente+" "+prix_achat);
				for(Action a : Action.values()){
					banque.getSolde_actions().put(a, Integer.MAX_VALUE);
					//prix plus à jour
					achat(banque, a, prix_vente, max_action_en_jeu);
					vend(banque, a, prix_achat, max_action_en_jeu);
				}
			}
		}
	}

	public boolean est_ouvert() {
		return ouvert;
	}

	public void commence() {
		ouvert = true;
		debut = System.currentTimeMillis();

		timer = new Thread() {
			@Override
			public void run() {
				try {
					Thread.sleep(1000 * 60 * Config.getInstance().TEMPS_PARTIE);
				} catch (InterruptedException e) {
					//might be interrupted if creator leaves
					//don't print error in this case
				}
				fini = true;
				try {
					Thread.sleep(1000); // sleep 1 s to be sure fini is taking into account
				} catch (InterruptedException e) {
					//might be interrupted if creator leaves
					//don't print error in this case
				}
				//fermer tous les ordres
				for(Joueur joueur : liste_joueurs){
					List<Integer> ordre_supprimer = new LinkedList<>();
					for(Pair<Integer, Ordre> t : joueur.getOperationsOuvertes()){
						ordre_supprimer.add(t.getLeft());
					}
					for(Integer i : ordre_supprimer){
						annuler(joueur, i);
					}
				}
			}
		};
		timer.start();
	}

	public boolean est_fini() {
		return fini;
	}

	// synchronized pour les rares appels de gestion de joueur
	public synchronized Joueur creer_joueur(String nom, String nom_complet) {
		Joueur j = new Joueur(nom, initial_euros, nom_complet);
		liste_joueurs.add(j);
		return j;
	}

	public synchronized boolean nom_possible(String nom) {
		for (Joueur j : liste_joueurs)
			if (j.getNom().equalsIgnoreCase(nom))
				return false;
		return true;
	}

	//synchronized est moins efficace car il bloque les lectures concurrentes
	public String getListeAchatsString(Action a, int nbMaxAchats) {
		mutex_ordre_read.lock();
		String r;
		if(nbMaxAchats<=0 || nbMaxAchats>=liste_achats.get(a).size())
			r = String.valueOf(liste_achats.get(a));
		else{
			LinkedList<Ordre> list = new LinkedList<Ordre>();
			final Iterator<Ordre> i = liste_achats.get(a).iterator();
			for (int j=0; j<nbMaxAchats && i.hasNext();j++)
				list.add(i.next());
			r=String.valueOf(list);
		}
		mutex_ordre_read.unlock();
		return r;
	}

	public String getListeVentesString(Action a, int nbMaxVentes) {
		mutex_ordre_read.lock();
		String r;
		if(nbMaxVentes<=0 || nbMaxVentes>liste_ventes.get(a).size())
			r = String.valueOf(liste_ventes.get(a));
		else{
			LinkedList<Ordre> list = new LinkedList<Ordre>();
			final Iterator<Ordre> i = liste_ventes.get(a).iterator();
			for (int j=0; j<nbMaxVentes && i.hasNext();j++)
				list.add(i.next());
			r=String.valueOf(list);
		}
		mutex_ordre_read.unlock();
		return r;
	}
	
	/**
	 * @param a : le nom de l'action
	 * @param n : le numéro de liste à partir duquel il faut envoyer les éléments historiques du serveur au client
	 * @return  : retourne une liste chainée contenant les éléments de 'historiques' voulus
	 */
	public LinkedList<Echange> getHistoriqueEchanges(Action a, int n) {
		mutex_ordre_read.lock();
		int tailleH=historiques.get(a).size(); //Pour calculer une seule fois la taille de la liste
		LinkedList<Echange> list = new LinkedList<Echange>();//on instancie la liste chainée que l'on va remplir
		
		//on crée un iterateur parcourant 'historiques' dans le sens décroissant
		final Iterator<Echange> i = historiques.get(a).descendingIterator();
		for (int j=0; j<tailleH-n && i.hasNext();j++)
			//on ajoute dans la liste chainée les éléments au sens décroissants de 'historiques'
			//au début de la liste chainée à chaque fois pour que les éléments apparaissent dans le bon sens
			list.addFirst(i.next());
		
		mutex_ordre_read.unlock();
		//on retourne la liste chainée
		return list;
	}

	public int achat(Joueur joueur_achat, Action a, float prix_achat, int volume_achat) {
		if (volume_achat <= 0.0)
			return -5;
		if (prix_achat <= 0.0)
			return -6;
		if((int)(volume_achat * prix_achat) == 0)
			return -8;

		mutex_ordre_write.lock();
		int argent_joueur= joueur_achat.getSolde_euros();
		int argent_engage = (int) (volume_achat * prix_achat);
		if (argent_joueur < argent_engage) {
			mutex_ordre_write.unlock();
			return -7;
		}

		if (!joueur_achat.getNom().equals("banque"))
			liste_Operations.add(new Operation("Achat",joueur_achat.getNom(),a.toString(),prix_achat,volume_achat));
		
		Iterator<Ordre> it = liste_ventes.get(a).iterator();

		while (it.hasNext() && volume_achat > 0) {
			Ordre vente = it.next();
			Joueur joueur_vente = vente.getJoueur();

			if (prix_achat >= vente.prix) {
				Echange e = new Echange(joueur_vente, joueur_achat, vente.prix, Math.min(vente.volume, volume_achat), echange_unique++);
				historiques.get(a).add(e);

				// l'achat est complete
				if (volume_achat <= vente.volume) {
					int volume_vendu = volume_achat;

					joueur_vente.setSolde_euros(joueur_vente.getSolde_euros() + (int) (vente.prix * volume_vendu));
					joueur_achat.setSolde_euros(joueur_achat.getSolde_euros() - (int) (Math.ceil(vente.prix * volume_vendu)));
					joueur_achat.getSolde_actions().put(a, joueur_achat.getSolde_actions().get(a) + volume_vendu);
					vente.setVolume(vente.getVolume() - volume_vendu);
					
					//ordre restant vide
					if(vente.getVolume() == 0){
						Integer id_vente = vente.getId_ordre();
						joueur_vente.retirerOperation(id_vente);
						it.remove();
					}

					mutex_ordre_write.unlock();
					return 0;
				}
				// volume_achat > vente.volume

				int volume_vendu = vente.volume;

				joueur_vente.setSolde_euros(joueur_vente.getSolde_euros() + (int) (vente.prix * volume_vendu));
				joueur_achat.setSolde_euros(joueur_achat.getSolde_euros() - (int) (Math.ceil(vente.prix * volume_vendu)));
				joueur_achat.getSolde_actions().put(a, joueur_achat.getSolde_actions().get(a) + volume_vendu);
				vente.setVolume(vente.getVolume() - volume_vendu);

				// remove
				Integer id_vente = vente.getId_ordre();
				joueur_vente.retirerOperation(id_vente);
				it.remove();

				volume_achat -= volume_vendu;
			} else
				break;
		}

		// provisionne le reste
		joueur_achat.setSolde_euros(joueur_achat.getSolde_euros() - (int) (Math.ceil(prix_achat * volume_achat)));

		int id = creer_id_ordre();
		Ordre achat = new Achat(id, a, prix_achat, volume_achat, joueur_achat, ordre_unique++);
		liste_achats.get(a).add(achat);
		joueur_achat.getOperationsOuvertes().add(Pair.of(id, achat));
		mutex_ordre_write.unlock();
		return id;
	}

	public int vend(Joueur joueur_vente, Action a, float prix_vente, int volume_vente) {
		if (volume_vente <= 0.0)
			return -8;
		if (prix_vente <= 0.0)
			return -9;
		if((int)(volume_vente * prix_vente) == 0)
			return -11;

		mutex_ordre_write.lock();
		int volume_joueur = joueur_vente.getSolde_actions().get(a);
		if (volume_joueur < volume_vente) {
			mutex_ordre_write.unlock();
			return -10;
		}

		if (!joueur_vente.getNom().equals("banque"))
			liste_Operations.add(new Operation("Vente",joueur_vente.getNom(),a.toString(),prix_vente,volume_vente));
		
		joueur_vente.getSolde_actions().put(a, volume_joueur - volume_vente);
		Iterator<Ordre> it = liste_achats.get(a).iterator();

		while (it.hasNext() && volume_vente > 0) {
			Achat achat = (Achat) it.next();
			Joueur joueur_achat = achat.getJoueur();

			if (prix_vente <= achat.prix) {
				Echange e = new Echange(joueur_vente, joueur_achat, achat.prix, Math.min(achat.volume, volume_vente), echange_unique++);
				historiques.get(a).add(e);

				// la vente est complete
{'fraction_non_alphanumeric': 0.13071122283010497, 'fraction_numerical': 0.006505988466656809, 'mean_word_length': 4.191914022517912, 'pattern_counts': {'":': 2, '<': 95, '<?xml version=': 0, '>': 81, 'https://': 0, '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': '3379622', 'n_tokens_mistral': 8702, 'n_tokens_neox': 7638, 'n_words': 1525}