///Licensed under Creative Commons Attribution 4.0 License, Written by Clemens Winter and Vladislav Nachev

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;

namespace Exploitation
{

    class Flower : Ressource
    {
        static SimpleRNG rnd = new SimpleRNG();
        private long timeLastProductionStep;

        private double startNectar;
        private double nectar;
        public double nectarSugarProduction;
        private double nectarVolumeProduction;
        private double nectarMinReward;
        private int maxNectar;
        public string genotype;
        private double concentration;
        private int flowerType;
        private string vol1;
        private double vol1probability;
        private double vol2;
        private double vol2probability;
        private int[] rewSequence;
        private string[] rewPhases;
        public List<string> samen = new List<string>();

        private int anzahlBesuche;
        private double specificNectarEnergy;

        /// <summary>
        /// Defines a resource, flower, with several specific traits
        /// </summary>
        /// <param name="id">flower id number</param>
        /// <param name="xPos">position on the x axis (currently irrelevant)</param>
        /// <param name="yPos">position on the y axis (currently irrelevant)</param>
        /// <param name="zPos">position on the z axis (currently irrelevant)</param>
        /// <param name="nectarSugarProduction">secretion rate, or sugar production rate, depends on flower type</param>
        /// <param name="startNectar">depending on flower type it gives either the initial amount in the nectar account (type 2,type 6)
        /// or the extra secretion time from which the amount is calculated (type 1)</param>
        /// <param name="maxNectar">maximal amount of nectar a flower can hold</param>
        /// <param name="genotype">genotype code, using four digits for the four genes, and values of 1 for heterozygous
        /// and 2 (e.g. BB) and 0 (e.g. aa) for homozygous genotypes. Example: AaBbCCdd = 1120</param>
        /// <param name="concentration">sugar concentration in % w/w</param>
        /// <param name="type">flower type
        /// Type 1: Concentration coded by genotype, specifically as the water amount added to fixed sugar amount. Trade-off between 
        /// nectar production rate and sugar concentration, Reward volume = starting value + (current time -start time) * (SPR + Water)
        /// Type 2: Concentration directly coded by genotype, without forced trade-offs between concentration and nectar production rate,
        /// Reward volume = starting value + (current time -start time)*(NPR)
        /// Type 3: Probabilistic reward delivery, see vol1, vol1prob, and vol2 below
        /// Type 4: Same as Type 1, but never delivers rewards smaller than the nectarMinReward, instead giving 0 rewards
        /// Type 5: Read rewards from a list given in vol1 input of feeder IDs with "-" as a separator. Blocks of rewards can be grouped as "10-0x50x0"
        /// the "x" gives how often the first block (10-0) repeats until the second block (0) activates. This can be used for feeders
        /// delivering different reward schemes beyond a certain number of visits
        /// Type 6: Fixed interval schedule; nectarVolumeProduction gives the time it takes for a flower to become baited. It is unbaited
        /// if visited sooner than this time interval, otherwise baited with value given in vol1 below
        /// </param>
        /// <param name="vol1">first reward volume alternative (type 3), or list of rewards (type 5), or fixed reward volume (type 6)</param>
        /// <param name="vol1prob">probability that vol1 will be the reward volume (vol2 delivered with p = 1 - vol1prob)</param>
        /// <param name="vol2">second reward volume alternative (type 3)</param>
        /// <param name="vol2prob">currently unused</param>
        public Flower(  int id, int xPos, int yPos, int zPos,  double nectarSugarProduction, double startNectar, int maxNectar,
                        string genotype, double concentration, int type, string vol1, double vol1prob, double vol2, double vol2prob) 
        : base(id, xPos, yPos, zPos, genotype)
        {
            this.startNectar = startNectar; 
            this.nectarSugarProduction = nectarSugarProduction;
            this.nectarMinReward = 0;
            this.maxNectar = maxNectar;
            this.genotype = genotype;
            this.flowerType = type;          

            switch (flowerType)
            {
                case 1: case 4:
                    //concentration converted from the genotype coded as "genotype", see above
                    this.concentration = PPO.Instance.NumtoConc(genotype);
                    //calculate the volume production per second from the suga production and the concentration;
                    //quadratic equation in denominator for converting %w/w into mg sugar/microliters of solution
                    nectarVolumeProduction = 1000 * nectarSugarProduction /
                        (0.05298 * this.concentration * this.concentration + 9.56955 * this.concentration + 3.32727);
                    this.nectar = startNectar * nectarVolumeProduction;
                    break;
                case 2:
                    this.concentration = concentration;
                    nectarVolumeProduction = nectarSugarProduction;
                    this.nectar = startNectar * nectarVolumeProduction;
                    break;
                case 5:
                    this.concentration = concentration;
                    rewPhases = (vol1).Split('x').ToArray();
                    rewSequence = (rewPhases[0]).Split('-').Select(num => int.Parse(num)).ToArray();
                    break;
                case 6:
                    this.concentration = concentration;
                    nectarVolumeProduction = nectarSugarProduction;
                    timeLastProductionStep = -Convert.ToInt64(nectarVolumeProduction);
                    this.nectar = startNectar;
                    break;
                default:
                    this.concentration = concentration;
                    nectarVolumeProduction = nectarSugarProduction;
                    this.nectar = startNectar;
                    break;
            }

            specificNectarEnergy = (15.96 * 1 /*Quantity*/ * 
                (0.05298 * this.concentration * this.concentration + 9.56955 * this.concentration + 3.32727)) / 1000000;
            this.vol1 = vol1;
            this.vol1probability = vol1prob ;
            this.vol2 = vol2;
            this.vol2probability = vol2prob ;


            SimpleRNG rnd = new SimpleRNG();
        }


        /// <summary>
        /// Collects a reward not larger than mealMax from a resource
        /// </summary>
        /// <param name="mealMax">maximal value of reward to be collected</param>
        /// <param name="time">current time in seconds</param>
        /// <param name="numVisit">visit number</param>
        /// <returns></returns>
        public override RewardStruct DeliverReward(int mealMax, long time, int numVisit)
        {
            double mealQuantity = 0; 
            double mealEnergy   = 0;
            anzahlBesuche++;
            switch (flowerType)
            {
                    // flowerType = 1: flower with evolving sugar concentration (water added to fixed sugar)
                    // Nectar account with constant sugar production and therefore with a trade-off between 
                    // NPR and concentration - defined over X, Y, Z coordinates, sugar production rate (SPR), 
                    // alleles for the water portion of nectar, starting and maximum account value 
                    // Reward volume = starting value + (current time -start time) * (SPR + Water)
                case 1:
                    nectar += nectarVolumeProduction * (time - timeLastProductionStep);
                    if (nectar > maxNectar) nectar = maxNectar;
                    timeLastProductionStep = time;

                    mealQuantity = mealMax < nectar ? mealMax : nectar;
                    if (mealQuantity < nectarMinReward) mealQuantity = 0;
                    nectar -= mealQuantity;

                    mealEnergy = mealQuantity * specificNectarEnergy;

                    // return: Quality, Quantity, Energy, "Transfer Particles"
                    return new RewardStruct(this.concentration, mealQuantity, mealEnergy);


                // flowerType = 2: Nectar account without forced trade-offs
                // defined over X, Y, Z coordinates,  total NPR, concentration, starting and maximum account value
                // Reward volume = starting value + (current time -start time)*(NPR)

                case 2:
                    nectar += nectarVolumeProduction * (time - timeLastProductionStep);
                    if (nectar > maxNectar) nectar = maxNectar;
                    timeLastProductionStep = time;
                    mealQuantity = mealMax < nectar ? mealMax : nectar;
                    if (mealQuantity < nectarMinReward) mealQuantity = 0;
                    nectar -= mealQuantity;
                    mealEnergy = mealQuantity * specificNectarEnergy;
                    return new RewardStruct(concentration, mealQuantity, mealEnergy);

                // probabilistic reward delivery
                case 3:
                    double probability = rnd.GetUniform();
                    
                    if (probability <= vol1probability)
                        return new RewardStruct(concentration, int.Parse(vol1), int.Parse(vol1) * specificNectarEnergy);
                    else
                        return new RewardStruct(concentration, vol2, vol2*specificNectarEnergy);

                //this flower type is the same as 1 but never delivers rewards smaller than the nectarMinReward but instead gives 0
                case 4:
                    nectar += nectarVolumeProduction * (time - timeLastProductionStep);
                    if (nectar > maxNectar) nectar = maxNectar;
                    timeLastProductionStep = time;

                    double floorednectar = Math.Floor(nectar);
                    mealQuantity = mealMax < floorednectar ? mealMax : floorednectar;
                    if (mealQuantity < nectarMinReward) mealQuantity = 0;
                    nectar -= mealQuantity;

                    mealEnergy = mealQuantity * specificNectarEnergy;

                    return new RewardStruct(this.concentration, mealQuantity, mealEnergy);

                // read rewards from a list of feeder IDs with "-" as a separator. Blocks of rewards can be grouped as "10-0x50x0"
                    // the "x" gives how often the first block (10-0) repeats until the second block (0) activates
                    // This can be used for feeders delivering different reward schemes beyond a certain number of visits
                case 5:

                    int vol = 0;
                    if(rewPhases.Length > 1) {
                        if (numVisit > int.Parse(rewPhases[1]))
                        {
                            rewSequence = (rewPhases[2]).Split('-').Select(num => int.Parse(num)).ToArray();
                        }
                        else
                        {
                            rewSequence = (rewPhases[0]).Split('-').Select(num => int.Parse(num)).ToArray();
                        }
                    }
                        vol  = ((numVisit) % rewSequence.Length) - 1;

                        if (vol  < 0) vol  = rewSequence.Length - 1;

                        return new RewardStruct(concentration, rewSequence[vol], rewSequence[vol] * specificNectarEnergy);

                // fixed interval schedule
                case 6:
                    
                    if (time - timeLastProductionStep  < nectarVolumeProduction )
                        // in this flower type nectarVolumeProduction gives the time it takes for a flower to become baited. It is unbaited
                        // if visited sooner than this time interval.
                    {
                        mealQuantity = 0;
                    }
                else
                    {
                        mealQuantity = int.Parse(vol1);
                    }

                    if (mealQuantity > 0) timeLastProductionStep = time;

                    return new RewardStruct(concentration, mealQuantity, mealQuantity * specificNectarEnergy);
                

                default: return new RewardStruct(0, 0, 0, 0);

            }

        }

        public override void  ResetRessources()

        {
               switch (flowerType)
               {
                   case 1:
                       nectar = startNectar * nectarVolumeProduction;
                       timeLastProductionStep = 0;

                       break;

                   case 2:
                       nectar = startNectar;
                       timeLastProductionStep = 0;
                       break;
                }

        }

        /// <summary>
        /// Obtain a single pollen grain from the current resource, if possible
        /// </summary>
        /// <returns>haploid genotype of the pollen grain obtained via Meiosis from the current resource</returns>
        internal override TransferParticles CheckForTransferToAgent()
            
        {
            if (genotype.Length == 4)
            { return new TransferParticles(Meiosis()); }
            else
            { return null; } // this is returned in case the genotype of the flower is not a 4-digit genotype
        }

        /// <summary>
        /// Generate a seed, via pollination of current resource with pollen carried by agent, if possible
        /// </summary>
        /// <param name="transferParticles">haploid genotype of the pollen grain obtained via Meiosis from the last visited resource</param>
        internal override void TransferToRessource(TransferParticles transferParticles)
        {
            if (transferParticles != null)
            {
                double[] k = Meiosis();
                string offspring = "";

                for (int i = 0; i < 4; i++)
                {
                    if (k[i] + (int)transferParticles[i] == 3)
                    {
                        k[i] = k[i];
                    }
                    offspring = offspring + (k[i]+(int)transferParticles[i]).ToString();
                }
                samen.Add(offspring);
                PPO.Instance.AddSeed(offspring);
             

            }
        }

        /// <summary>
        /// Obtain last seed generated
        /// </summary>
        /// <returns>genotype of last seed generated</returns>
        public override string getSeed()
        {
            if (samen.Count > 0)
                return samen.Last<string>();
            else return "3333"; //dummy invalid genotype, for invalid crosses that can be later ignored or removed
        }

        /// <summary>
        /// Generate a flower from a seed with a given genotype
        /// </summary>
        /// <param name="genome">genotype of the seed</param>
        /// <returns>flower resource object</returns>
        public Flower SeedToFlower(string genome)
        {
            return new Flower(id, XPos, YPos, ZPos, nectarSugarProduction, startNectar, maxNectar,
            genome, concentration, flowerType, vol1, vol1probability, vol2, vol2probability);
        }

        /// <summary>
        /// Generate a gamete from the current flower's genotype
        /// </summary>
        /// <returns>haploid genotype of a gamete</returns>
        public double[] Meiosis()
        {
            double[] meiosis = new double[4];

            for (int i = 0; i < 4; i++)
            {
                switch (int.Parse(genotype.Substring(i, 1)))
                {
                    case 0:
                        meiosis[i] = 0;
                        break;
                    case 1:
                        meiosis[i] = rnd.GetInt(0, 1);
                        break;
                    case 2:
                        meiosis[i] = 1;
                        break;
                }
            }

            return meiosis;
        }

    }
}
