// Awesome Profit Switching v1.7.7

public class ActionScript
{
    private ContextProvider Context = ScriptManager.Context;

    public bool Execute(List list)
    {
        Context.System.LogMessage("Awesome Profit Switching: " + list.Count + " miner(s).");
        MinerProcessor minerProcessor = new MinerProcessor();
        List poolProfitList = new List();

        // Download latest statistics before we check profitability
        Context.OnlineService.UpdateStatisticsAll();
        Context.System.LogMessage("Awesome Profit Switching: Statistics updated");

        // Process each selected miner
        foreach (IMinerBase miner in list)
        {
            if (!miner.IsRunningState)
                continue;

            Context.System.LogMessage("Awesome Profit Switching processing: " + miner.GetDescription());
            minerProcessor.Run(miner);
        }
        return true;
    }
}

class MinerProcessor
{
    private ContextProvider Context = ScriptManager.Context;
    public void Run(IMinerBase miner)
    {
        ISinglePool currentPool = Context.Pool.GetActive(miner);

        if (currentPool != null)
        {
            // Each miner has its own list of pools
            List minerPoolProfitList = new List();
            List preservePoolList = new List();

            // Get all active pools for the miner (for Primitive miners, include Disabled pools)
            List coinPoolList = Context.Pool.GetActivePoolList(miner, false, true, !miner.IsClassic);

            // Add all pools into minerPoolProfitList if it should be target for profit switching
            // or to preservePoolList if we can't switch away from that pool
            foreach (ISinglePool coinPool in coinPoolList)
            {
                PoolProfit poolProfit = new PoolProfit(coinPool);
                if (poolProfit.IsValid())
                {
                    IPoolStatus poolStatus = miner.GetPoolStatus(poolProfit.Pool);
                    if (poolStatus != null)
                    {
                        poolProfit.Priority = poolStatus.GetPriority();
                        if (poolProfit.IsPreservePool)
                            preservePoolList.Add(poolProfit);
                        else
                            minerPoolProfitList.Add(poolProfit);
                    }
                    else
                        Context.System.LogMessage("  No pool status available: " + poolProfit.Pool.GetDescription());
                }
                else if (poolProfit.Pool != null)
                    Context.System.LogMessage("  Pool profit information not available: " + poolProfit.Pool.GetDescription());
            }

            // Sort to get highest profit first in list
            minerPoolProfitList.Sort((item1, item2) => item2.GetProfit().CompareTo(item1.GetProfit()));

            // Sort preseve pools by Priority order, lowest number first
            preservePoolList.Sort((item1, item2) => item1.Priority.CompareTo(item2.Priority));

            LogList("Profitability information", minerPoolProfitList);
            LogList("Preserve list", preservePoolList);

            // Construct list of pools in priority order. First the pools to preserve at top priority and then by profitability
            List priorityList = new List();
            preservePoolList.ForEach(item => priorityList.Add(item.Pool));
            minerPoolProfitList.ForEach(item => priorityList.Add(item.Pool));
            if (!miner.IsClassic)
            {
                // For CcMiner and Eth miner
                if (coinPoolList.Count > 0 && priorityList.Count > 0 && coinPoolList[0].ID != priorityList[0].ID)
                {
                    Context.System.LogMessage("Profitability, Changing to pool: " + priorityList[0].GetDescription());
                    Context.Pool.Prioritize(miner, priorityList[0]);
                }
                else
                    Context.System.LogMessage("Profitability, Already using pool: " + priorityList[0].GetDescription());
            }
            else
            {
                Context.Pool.SetPriority(miner, priorityList);
            }
        }
    }

    private void LogList(string message, List profitList)
    {
        string info = string.Empty;
        foreach (PoolProfit poolProfit in profitList)
            info += "  " + poolProfit.Pool.GetDescription() + ", Url: " + poolProfit.Pool.GetUrl() +
            ", Profit: " + poolProfit.GetProfit() + ", Priority: " + poolProfit.Priority + "\r\n";
        Context.System.LogMessage(message + ":\r\n  " + info.Trim());
    }
}

class PoolProfit
{
    // To disable an algorithm, set factor to 0.
    // To make it less likely to be used, set to lower value than 1.0.
    // Using 0.5 would require the profit to be two times as high before selecting the algorithm
    private Dictionary algorithmFactor = new Dictionary {
        { CoinAlgorithm.Scrypt, 1.0 },
        { CoinAlgorithm.ScryptAN, 1.0 },
        { CoinAlgorithm.SHA3, 1.0 },
        { CoinAlgorithm.X11, 1.0 },
        { CoinAlgorithm.X13, 1.0 },
        { CoinAlgorithm.X15, 1.0 },
        { CoinAlgorithm.Nist5, 1.0 },
        { CoinAlgorithm.NeoScrypt, 1.0 },
        { CoinAlgorithm.Qubit, 1.0 },
        { CoinAlgorithm.WhirlpoolX, 1.0 },
        { CoinAlgorithm.Quark, 1.0 },
        { CoinAlgorithm.Lyra2REv2, 1.0 },
    };

    private Dictionary poolServices = new Dictionary {
        // NiceHash standard pools
        {"nicehash.com:3333", new PoolService(OnlineServiceType.NiceHash, CoinAlgorithm.Scrypt)},
        {"nicehash.com:3335", new PoolService(OnlineServiceType.NiceHash, CoinAlgorithm.ScryptAN)},
        {"nicehash.com:3338", new PoolService(OnlineServiceType.NiceHash, CoinAlgorithm.SHA3)},
        {"nicehash.com:3336", new PoolService(OnlineServiceType.NiceHash, CoinAlgorithm.X11)},
        {"nicehash.com:3337", new PoolService(OnlineServiceType.NiceHash, CoinAlgorithm.X13)},
        {"nicehash.com:3339", new PoolService(OnlineServiceType.NiceHash, CoinAlgorithm.X15)},
        {"nicehash.com:3340", new PoolService(OnlineServiceType.NiceHash, CoinAlgorithm.Nist5)},
        {"nicehash.com:3341", new PoolService(OnlineServiceType.NiceHash, CoinAlgorithm.NeoScrypt)},
        {"nicehash.com:3342", new PoolService(OnlineServiceType.NiceHash, CoinAlgorithm.Lyra2RE)},
        {"nicehash.com:3343", new PoolService(OnlineServiceType.NiceHash, CoinAlgorithm.WhirlpoolX)},
        {"nicehash.com:3344", new PoolService(OnlineServiceType.NiceHash, CoinAlgorithm.Qubit)},
        {"nicehash.com:3345", new PoolService(OnlineServiceType.NiceHash, CoinAlgorithm.Quark)},
        {"nicehash.com:3347", new PoolService(OnlineServiceType.NiceHash, CoinAlgorithm.Lyra2REv2)},
        {"nicehash.com:3354", new PoolService(OnlineServiceType.NiceHash, CoinAlgorithm.Decred)},
        {"nicehash.com:3356", new PoolService(OnlineServiceType.NiceHash, CoinAlgorithm.Lbry)},
        
        // LTCRabbit
        {"ltcrabbit.com:3333", new PoolService(OnlineServiceType.LtcRabbit, CoinAlgorithm.Scrypt)},
        {"ltcrabbit.com:3335", new PoolService(OnlineServiceType.LtcRabbit, CoinAlgorithm.Scrypt)},
        {"ltcrabbit.com:3336", new PoolService(OnlineServiceType.LtcRabbit, CoinAlgorithm.Scrypt)},
        {"ltcrabbit.com:3337", new PoolService(OnlineServiceType.LtcRabbit, CoinAlgorithm.Scrypt)},
        {"ltcrabbit.com:3338", new PoolService(OnlineServiceType.LtcRabbit, CoinAlgorithm.Scrypt)},
        {"ltcrabbit.com:3332", new PoolService(OnlineServiceType.LtcRabbit, CoinAlgorithm.X11)},

        // zpool.ca
        {"zpool.ca:3433", new PoolService(OnlineServiceType.Zpool, CoinAlgorithm.Scrypt)},
        {"zpool.ca:3533", new PoolService(OnlineServiceType.Zpool, CoinAlgorithm.X11)},
        {"zpool.ca:3633", new PoolService(OnlineServiceType.Zpool, CoinAlgorithm.X13)},
        {"zpool.ca:3733", new PoolService(OnlineServiceType.Zpool, CoinAlgorithm.X15)},
        {"zpool.ca:3833", new PoolService(OnlineServiceType.Zpool, CoinAlgorithm.Nist5)},
        {"zpool.ca:4233", new PoolService(OnlineServiceType.Zpool, CoinAlgorithm.NeoScrypt)},
        {"zpool.ca:4733", new PoolService(OnlineServiceType.Zpool, CoinAlgorithm.Qubit)},
        {"zpool.ca:4033", new PoolService(OnlineServiceType.Zpool, CoinAlgorithm.Quark)},
        {"zpool.ca:4533", new PoolService(OnlineServiceType.Zpool, CoinAlgorithm.Lyra2REv2)},
        {"zpool.ca:5744", new PoolService(OnlineServiceType.Zpool, CoinAlgorithm.Decred)},

    };

    // Always put these pools on top priority, before all profitability pools
    private List preservePoolServices = new List() {
        "betarigs.com",
        "miningrigrentals.com",
    };

    public bool IsPreservePool { get; private set; }
    public int Priority { get; set; }
    public ISinglePool Pool { get; private set; }
    public OnlineServiceType ServiceType { get; private set; }
    public CoinAlgorithm Algorithm { get; private set; }
    public double Profit { get; private set; }
    private PoolServiceClass PoolClass { get; set; }
    private int Port { get; set; }

    private class PoolService
    {
        public OnlineServiceType ServiceType { get; private set; }
        public CoinAlgorithm Algorithm { get; private set; }
        public PoolService(OnlineServiceType serviceType, CoinAlgorithm algorithm)
        {
            ServiceType = serviceType;
            Algorithm = algorithm;
        }
    }

    private enum PoolServiceClass
    {
        UserDefined,
        MultiCoinPool,
        SingleCoinPool,
    }

    public PoolProfit(ISinglePool pool)
    {
        Pool = pool;
        if (pool != null)
        {
            string url = pool.GetUrl().ToLower();
            // Check if a preserve-pool like Betarigs that we shouldn't prioritize away
            IsPreservePool = preservePoolServices.Find(item => url.Contains(item)) != null;

            if (!IsPreservePool)
            {
                // Search for ServiceType (supported multi-pools) using Pool URL
                PoolService poolService = null;
                foreach (KeyValuePair kvp in poolServices)
                {
                    if (url.Contains(kvp.Key))
                    {
                        poolService = kvp.Value;
                        break;
                    }
                }

                if (poolService != null)
                {
                    // Multi-pool service
                    ServiceType = poolService.ServiceType;
                    Algorithm = poolService.Algorithm;
                    PoolClass = PoolServiceClass.MultiCoinPool;
                }
                else
                {
                    // Check if known single coin pool (TradeMyBit)
                    OnlineServiceType serviceType;
                    CoinAlgorithm algorithm;
                    int port;
                    if (ScriptManager.Context.OnlineService.GetKnownSingleCoinPool(url, out serviceType, out algorithm, out port))
                    {
                        ServiceType = serviceType;
                        Algorithm = algorithm;
                        Port = port;
                        PoolClass = PoolServiceClass.SingleCoinPool;
                    }
                    else // Any other single coin pool
                    {
                        PoolClass = PoolServiceClass.UserDefined;
                        Algorithm = ScriptManager.Context.Pool.GetPoolAlgorithm(pool);
                    }
                }
            }
        }
    }

    public double GetProfit()
    {
        if (PoolClass == PoolServiceClass.MultiCoinPool)
        {
            IOnlineServiceEntry entry = ScriptManager.Context.OnlineService.GetEntry(ServiceType, Algorithm);
            return (entry != null) ? entry.BtcPerNormalizedMhsPerDay * GetAlgorithmFactor() : 0;
        }
        else if (PoolClass == PoolServiceClass.SingleCoinPool)
        {
            IOnlineServiceEntry entry = ScriptManager.Context.OnlineService.GetEntry(ServiceType, Port);
            return (entry != null) ? entry.BtcPerNormalizedMhsPerDay * GetAlgorithmFactor() : 0;
        }
        else
            return ScriptManager.Context.Pool.GetPoolRevenuePerDay(Pool, true) * GetAlgorithmFactor();
    }

    public bool IsValid()
    {
        return Pool != null && (PoolClass != PoolServiceClass.MultiCoinPool || ScriptManager.Context.OnlineService.GetEntry(ServiceType, Algorithm) != null);
    }

    private double GetAlgorithmFactor()
    {
        if (algorithmFactor.ContainsKey(Algorithm))
            return algorithmFactor[Algorithm];
        else
            return 1;
    }


}