package net.sf.rails.game.financial;

import java.util.Iterator;
import java.util.List;
import java.util.SortedSet;

import net.sf.rails.game.GameDef;
import net.sf.rails.game.Player;
import net.sf.rails.game.PublicCompany;
import net.sf.rails.game.GameDef.Parm;
import net.sf.rails.game.financial.PublicCertificate.Combination;
import net.sf.rails.game.model.CertificatesModel;
import net.sf.rails.game.state.Portfolio;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSortedSet;

/**
 * PlayerShareUtils is a class with static methods around president changes etc.
 */
public class PlayerShareUtils {
    
    public static SortedSet<Integer> sharesToSell (PublicCompany company, Player player) {
        
        if (company.hasMultipleCertificates()) {
            if (player == company.getPresident()) {
                return presidentSellMultiple(company, player);
            } else {
                return otherSellMultiple(company, player);
            }
        } else {
            if (player == company.getPresident()) {
                return presidentSellStandard(company, player);
            } else {
                return otherSellStandard(company, player);
            }
        }
        
    }
    
    public static int poolAllowsShareNumbers(PublicCompany company) {
        int poolShares = Bank.getPool(company).getPortfolioModel().getShareNumber(company);
        int poolMax = (GameDef.getGameParameterAsInt(company, GameDef.Parm.POOL_SHARE_LIMIT) / company.getShareUnit() 
                - poolShares);
        return poolMax;
    }
    
    // President selling for companies WITHOUT multiple certificates
    private static SortedSet<Integer> presidentSellStandard(PublicCompany company, Player president) {
        int presidentShares = president.getPortfolioModel().getShareNumber(company);
        int poolShares = poolAllowsShareNumbers(company);
        
        // check if there is a potential new president ...
        int presidentCertificateShares = company.getPresidentsShare().getShares();
        Player potential = company.findPlayerToDump();

        int maxShares;
        if (potential == null) {
            // ... if there is none, selling is only possible until the presidentCerificate or pool maximum
            maxShares = Math.min(presidentShares - presidentCertificateShares, poolShares);
        } else { 
            // otherwise until pool maximum only
            maxShares = Math.min(presidentShares, poolShares);
        }
         
        ImmutableSortedSet.Builder<Integer> sharesToSell = ImmutableSortedSet.naturalOrder();
        for (int s=1; s <= maxShares; s++) {
            sharesToSell.add(s);
        }
        return sharesToSell.build();
    }
    
    // Non-president selling for companies WITHOUT multiple certificates
    private static SortedSet<Integer> otherSellStandard(PublicCompany company, Player player) {
        int playerShares = player.getPortfolioModel().getShareNumber(company);
        int poolShares = poolAllowsShareNumbers(company);
        
        ImmutableSortedSet.Builder<Integer> sharesToSell = ImmutableSortedSet.naturalOrder();
        for (int s=1; s <= Math.min(playerShares, poolShares); s++) {
            sharesToSell.add(s);
        }
        return sharesToSell.build();
    }
    
    
    // President selling for companies WITH multiple certificates
    private static SortedSet<Integer> presidentSellMultiple(PublicCompany company, Player president) {
        
        // first: check what number of shares have to be dumped
        int presidentShareNumber = president.getPortfolioModel().getShare(company);
        PublicCertificate presidentCert = company.getPresidentsShare();
        Player potential = company.findPlayerToDump();
        int potentialShareNumber = potential.getPortfolioModel().getShare(company);
        int shareNumberDumpDifference = presidentShareNumber - potentialShareNumber;
        boolean presidentShareOnly = false;
        
        if (presidentCert.getShare() == presidentShareNumber)  { // Only President Share to be sold...
            presidentShareOnly = true;
        }
        
        // ... if this is less than what the pool allows => goes back to non-president selling
        int poolAllows = poolAllowsShareNumbers(company);
        if ((shareNumberDumpDifference <= poolAllows) && (!presidentShareOnly)) {
            return otherSellMultiple(company, president);
        }
        
        // second: separate the portfolio into other shares and president certificate
        ImmutableList.Builder<PublicCertificate> otherCerts = ImmutableList.builder();
        for (PublicCertificate c:president.getPortfolioModel().getCertificates(company)) {
            if (!c.isPresidentShare()) {
                otherCerts.add(c);
            }
        }
        
        // third: retrieve the share number combinations of the non-president certificates
        SortedSet<Integer> otherShareNumbers = CertificatesModel.shareNumberCombinations(otherCerts.build(), poolAllows);
        
        // fourth: combine pool and potential certificates, those are possible returns
        ImmutableList.Builder<PublicCertificate> returnCerts = ImmutableList.builder();
        returnCerts.addAll(Bank.getPool(company).getPortfolioModel().getCertificates(company));
        returnCerts.addAll(potential.getPortfolioModel().getCertificates(company));
        SortedSet<Integer> returnShareNumbers = CertificatesModel.shareNumberCombinations(returnCerts.build(), presidentCert.getShares());
        
        ImmutableSortedSet.Builder<Integer> sharesToSell = ImmutableSortedSet.naturalOrder();
        for (Integer s:otherShareNumbers) {
            if (s <= shareNumberDumpDifference){
                // shareNumber is below or equal the dump difference => add as possibility to sell without dump
                sharesToSell.add(s);
            }
            // now check if there are dumping possibilities
            for (int d=1; d <= presidentCert.getShares(); d++) {
                if (s+d <= poolAllows) { 
                    // d is the amount sold in addition to standard shares, returned has the remaining part of the president share
                    int remaining = presidentCert.getShares() - d;
                    if (returnShareNumbers.contains(remaining)) {
                        sharesToSell.add(s+d);
                    }
                } else {
                    break; // pool is full
                }
            }
        }
        return sharesToSell.build();
    }
    
    // Non-president selling for companies WITH multiple certificates
    private static SortedSet<Integer> otherSellMultiple(PublicCompany company, Player player) {
        
        // check if there is a multiple certificate inside the portfolio
        if (player.getPortfolioModel().containsMultipleCert(company)) {
            int poolAllows = poolAllowsShareNumbers(company);
            SortedSet<PublicCertificate> certificates = player.getPortfolioModel().getCertificates(company);
            return CertificatesModel.shareNumberCombinations(certificates, poolAllows);
        } else { // otherwise standard case
            return otherSellStandard(company, player);
        }
    }

    // FIXME: Rails 2.x This is a helper function as long as the sold certificates are not stored
    public static int presidentShareNumberToSell(PublicCompany company, Player president, Player dumpedPlayer,  int nbCertsToSell) {
        int dumpThreshold = president.getPortfolioModel().getShareNumber(company) - dumpedPlayer.getPortfolioModel().getShareNumber(company);
        if (nbCertsToSell > dumpThreshold) {
            // reduce the nbCertsToSell by the presidentShare (but it can be sold partially...)
            return Math.min(company.getPresidentsShare().getShares(), nbCertsToSell);
        } else {
            return 0;
        }
    }
    
    // FIXME: Rails 2.x This is a helper function as long as the sold certificates are not stored
    public static List<PublicCertificate> findCertificatesToSell(PublicCompany company, Player player, int nbCertsToSell, int shareUnits) {
  
        // check for <= 0 => empty list
        if (nbCertsToSell <= 0) {
            return ImmutableList.of();
        }
        
        ImmutableList.Builder<PublicCertificate> certsToSell = ImmutableList.builder();
        for (PublicCertificate cert:player.getPortfolioModel().getCertificates(company)) {
            if (!cert.isPresidentShare() && cert.getShares() == shareUnits) {
                certsToSell.add(cert);
                nbCertsToSell--;
                if (nbCertsToSell == 0) {
                    break;
                }
            }
                else if (cert.isPresidentShare() && cert.getShares()== shareUnits) {
                    certsToSell.add(cert);
                    nbCertsToSell--;
                    if (nbCertsToSell == 0) {
                        break;
                }
            }
        }
        
        return certsToSell.build();
    }
    
    public static void executePresidentTransferAfterDump(PublicCompany company, Player newPresident, BankPortfolio bankTo, int presSharesToSell) {
        
        // 1. Move the swap certificates from new president to the pool
        PublicCertificate presidentCert = company.getPresidentsShare();

        // ... get all combinations for the presidentCert share numbers
        SortedSet<Combination> combinations = CertificatesModel.certificateCombinations(
                newPresident.getPortfolioModel().getCertificates(company), presidentCert.getShares());
    
        // ... move them to the Bank
        // FIXME: this should be based on a selection of the new president, however it chooses the combination with most certificates
        Combination swapToBank = combinations.last();
        Portfolio.moveAll(swapToBank, bankTo);
        
        // 2. Move the replace certificates from the bank to the old president
        
        // What is the difference between the shares to sell and the president share number
        int replaceShares = presidentCert.getShares() - presSharesToSell;
        if (replaceShares > 0) {
            combinations = CertificatesModel.certificateCombinations(
                    bankTo.getPortfolioModel().getCertificates(company), replaceShares);
            // FIXME: this should be based on a selection of the previous president, however it chooses the combination with least certificates
            Combination swapFromBank = combinations.first();
            // ... move to (old) president
            Portfolio.moveAll(swapFromBank, company.getPresident());
        }
        
        // 3. Transfer the president certificate
        presidentCert.moveTo(newPresident);
    }
    
}