
@inject IDataCollectionService DataCollectionService
@using Model
@inherits BasePage

@page "/harass-calculator"

<LayoutMediumContentComponent>
    <WebsiteTitleComponent>Harass Calculator</WebsiteTitleComponent>

    <PaperComponent>
        Credit to Zard for deriving the formula.
    </PaperComponent>

    <PaperComponent>
        <LayoutRowComponent>
            <LayoutColumnComponent>
                <FormLayoutComponent>
                    <FormDisplayComponent Label="Cost of worker">
                        <Display>@CostOfWorker</Display>
                    </FormDisplayComponent>

                    <FormDisplayComponent Label="Alloy mined per second by worker">
                        <Display>@AlloyMinedPerSecondByWorker</Display>
                    </FormDisplayComponent>

                    <FormDisplayComponent Label="Time to produce worker">
                        <Display>@TimeToProduceWorker</Display>
                    </FormDisplayComponent>
                </FormLayoutComponent>
            </LayoutColumnComponent>

            <LayoutColumnComponent>
                <FormLayoutComponent>
                    <FormNumberComponent Min="1"
                                         Id="numberOfWorkersLostToHarass"
                                         Value="@((int)NumberOfWorkersLostToHarass)"
                                         OnChange="@(e =>
                                                   {
                                                       NumberOfWorkersLostToHarass = int.Parse(e.Value!.ToString()!);
                                                       Calculate();
                                                   })">
                        <FormLabelComponent>Number of workers lost to harass</FormLabelComponent>
                    </FormNumberComponent>

                    <FormNumberComponent Min="1"
                                         Id="numberOfTownHallsExisting"
                                         Value="@((int)NumberOfTownHallsExisting)"
                                         OnChange="OnTownHallsChanged">
                        <FormLabelComponent>Number of townhalls you have</FormLabelComponent>
                    </FormNumberComponent>

                    <div id="numberOfTownHallTravelTimes">
                        @{
                            var index = 0;
                        }
                        @foreach (var travelTime in TravelTimes)
                        {
                            index++;
                            if (index == 1)
                            {
                                continue;
                            }

                            var id = $"numberOfTownHallsExisting_{index}";
                            <FormNumberComponent Min="0"
                                                 Id="@id"
                                                 Value="@((int)travelTime.Value)"
                                                 OnChange="e => { OnTownHallTravelTimeChanged(e, travelTime); }">
                                <FormLabelComponent>Worker travel time from other
                                    base @(travelTime.Index + 1)</FormLabelComponent>
                            </FormNumberComponent>
                        }
                    </div>


                    <FormDisplayComponent Label="Total alloy lost">
                        <Display>
                            <div style="font-size: 1.5rem; font-weight: 800;">
                                <span id="totalAlloyHarassment">
                                    @TotalAlloyHarassment
                                </span>
                            </div>
                        </Display>
                    </FormDisplayComponent>


                </FormLayoutComponent>

                <br/>
                <div>
                    (<b>Worker replacement costs:</b> <span id="workerReplacementCost">@WorkerReplacementCost()</span>)
                </div>
                <div>
                    (<b>Delayed mining time:</b> <span id="delayedMiningCost">@DelayedMiningCost()</span>)
                </div>
                <div>
                    (<b>Average travel time:</b> <span id="getAverageTravelTime">@GetAverageTravelTime()</span>)
                </div>

            </LayoutColumnComponent>
        </LayoutRowComponent>
    </PaperComponent>

    <ContentDividerComponent></ContentDividerComponent>

    <PaperComponent>
        <InfoBodyComponent>
            <InfoQuestionComponent>
                What is this tool?
            </InfoQuestionComponent>
            <InfoAnswerComponent>
                The Harass Calculator allows you to calculate damage done to an enemy alloy line. For example, if you
                were to attack with Ichors, and kill 6 enemy workers, you can set the
                <b>
                    Number of workers lost to
                    harass
                </b> to 6. This would determine a loss of <span id="exampleTotalAlloyLoss">@ExampleTotalAlloyLoss</span>
                alloy. Quite
                the large number.
            </InfoAnswerComponent>
        </InfoBodyComponent>

        <InfoBodyComponent>
            <InfoQuestionComponent>
                What can I learn from this?
            </InfoQuestionComponent>
            <InfoAnswerComponent>
                Well, let's assume you lost a full alloy line of workers, and have to take that
                @ExampleTotalAlloyLoss alloy cost (<span id="exampleWorkerCost">@ExampleWorkerCost</span>
                to rebuy the workers, and <span id="exampleMiningTimeCost">@ExampleMiningTimeCost</span> in lost mining
                time.)
                <br/><br/>
                If you were to set the <b>Number of townhalls you have</b> to 2, the calculator will consider worker
                transfer micro. Allowing you to cut the total cost by roughly
                <span id="exampleTotalAlloyLossDifference">@ExampleTotalAlloyLossDifference</span> alloy. However, that
                number isn't
                entirely accurate, you are also going to have to bump up the <b>Worker travel time to alloy</b> to
                account for the time it takes the transferred workers to arrive at the decimated alloy line.
                <br/><br/>
                Let's say it takes 10 seconds for workers to transfer from your second base. Let's enter that for the
                second base travel time for the more accurate loss of
                <span
                    id="exampleTotalAlloyLossAccurate">
                    @ExampleTotalAlloyLossAccurate
                </span> alloy
                (saving you <span
                    id="exampleTotalAlloyLossAccurateDifference">@ExampleTotalAlloyLossAccurateDifference</span> alloy.)
                <i>
                    Which is
                    much better than not transferring workers!
                </i>
            </InfoAnswerComponent>
        </InfoBodyComponent>

        <InfoBodyComponent>
            <InfoQuestionComponent>
                Can I see the formula for the calculation?
            </InfoQuestionComponent>
            <InfoAnswerComponent>
                The Harass Calculator is based on the following calculation.

                <br/>
                <div class="mathContainer">
                    <div> =c*m+r*g*(t+l) +</div>

                    <MathLoopSumComponent>
                        <LoopStart><i>x</i> =1</LoopStart>
                        <LoopEnd>
                            ?
                            <MathDivisionComponent>
                                <Dividee>m</Dividee>
                                <Divider>a</Divider>
                            </MathDivisionComponent>
                            ?
                        </LoopEnd>
                        <IndexSymbol>
                            <i>x</i>
                        </IndexSymbol>
                    </MathLoopSumComponent>

                    <div style="width: 132px">g*<i>x</i>*(t+l)</div>
                </div>
                <br/>
                <div style="font-family:monospace;">
                    <div>c is CostOfWorker</div>
                    <div>m is NumberOfWorkersLostToHarass, <i>m for [M]otes</i></div>
                    <div>a is NumberOfTownHallsExisting, <i>a for [A]cropolis</i></div>
                    <div>r is m mod a is LeftOverWorkersToProduceCount()</div>
                    <div>g is AlloyMinedPerSecondByWorker</div>
                    <div>t is TimeToProduceWorker</div>
                    <div>l is TravelTime</div>
                    <div><i>x</i> is workerProductionIndex</div>
                </div>
                <br/>
                This logic has since been changed slightly to allow client to enter different travel times per base.
                <br/>
            </InfoAnswerComponent>
        </InfoBodyComponent>
        <InfoBodyComponent>
            <InfoQuestionComponent>
                Can I see the code for the calculation?
            </InfoQuestionComponent>
            <InfoAnswerComponent>
                <br/>
                <LinkButtonComponent
                    Href="https://git.jonathanmccaffrey.ca/JonathanMcCaffrey/IGP-Fan-Reference/src/branch/main/IGP/Pages/HarassCalculatorPage.razor#L226">
                    View Here
                </LinkButtonComponent>
            </InfoAnswerComponent>
        </InfoBodyComponent>
    </PaperComponent>


</LayoutMediumContentComponent>

<style>
    .mathContainer {
        font-family: monospace;
        display: flex;
        flex-direction: row;
        align-items: flex-start;
        border: 1px black solid;
        padding: 32px;
        background-color: #1A1B1E
    }

    @@media only screen and (max-width: 1025px) {
        .mathContainer {
            padding-left: 2px;
            padding-right: 2px;
        }
    }
</style>

@code {

    // Example calcs
    float ExampleTotalAlloyLoss => Calculate(
        WorkerReplacementCost(6),
        SimultaneousProductionFloor(1, 6),
        6,
        new List<float> { 0 });

    float ExampleWorkerCost => WorkerReplacementCost(6);

    float ExampleMiningTimeCost => ExampleTotalAlloyLoss - ExampleWorkerCost;

    float ExampleTotalAlloyLossDifference => ExampleTotalAlloyLoss - Calculate(
        WorkerReplacementCost(6),
        SimultaneousProductionFloor(2, 6),
        6,
        new List<float> { 0, 0 });

    float ExampleTotalAlloyLossAccurate => Calculate(
        WorkerReplacementCost(6),
        SimultaneousProductionFloor(2, 6),
        6,
        new List<float> { 0, 10 });

    float ExampleTotalAlloyLossAccurateDifference => ExampleTotalAlloyLoss - ExampleTotalAlloyLossAccurate;

    float TotalAlloyHarassment;

    readonly float CostOfWorker = 50;
    readonly float AlloyMinedPerSecondByWorker = 1;
    readonly float TimeToProduceWorker = 20;
    float NumberOfWorkersLostToHarass = 1;
    float NumberOfTownHallsExisting = 1;

    float GetAverageTravelTime()
    {
        if (TravelTimes.Count == 0)
        {
            return 0;
        }

        float sum = 0;

        foreach (var travelTime in TravelTimes)
        {
            sum += travelTime.Value;
        }

        return sum / NumberOfTownHallsExisting;
    }


    float SimultaneousProductionFloor()
    {
        if (NumberOfTownHallsExisting <= 0 || NumberOfWorkersLostToHarass <= 0)
        {
            return 0;
        }

        return NumberOfWorkersLostToHarass / Math.Min(NumberOfTownHallsExisting, NumberOfWorkersLostToHarass);
    }

    float SimultaneousProductionFloor(float existingTownHalls, float numberOfWorkersLost)
    {
        if (existingTownHalls <= 0 || numberOfWorkersLost <= 0)
        {
            return 0;
        }

        return numberOfWorkersLost / Math.Min(existingTownHalls, numberOfWorkersLost);
    }

    float WorkerReplacementCost()
    {
        return CostOfWorker * NumberOfWorkersLostToHarass;
    }

    float WorkerReplacementCost(int numberOfWorkersLostToHarass)
    {
        return CostOfWorker * numberOfWorkersLostToHarass;
    }

    float DelayedMiningCost()
    {
        return TotalAlloyHarassment - WorkerReplacementCost();
    }

    void Calculate()
    {
        TotalAlloyHarassment = Calculate(WorkerReplacementCost(),
            SimultaneousProductionFloor(),
            NumberOfWorkersLostToHarass,
            TravelTimes.Select(x => x.Value).ToList(),
            TimeToProduceWorker,
            AlloyMinedPerSecondByWorker);
    }

    float Calculate(float workerReplacementCost,
        float simultaneousProductionFloor,
        float numberOfWorkersLostToHarass,
        IList<float> travelTimes,
        float timeToProduceWorker = 20,
        float alloyMinedPerSecondByWorker = 1)
    {
        var totalAlloyHarassment = workerReplacementCost;

        for (var workerProductionIndex = 0; workerProductionIndex < simultaneousProductionFloor; workerProductionIndex++)
        {
            totalAlloyHarassment += alloyMinedPerSecondByWorker * timeToProduceWorker * (workerProductionIndex + 1);
        }

        var remainder = (int)(numberOfWorkersLostToHarass % simultaneousProductionFloor);
        for (var remainderIndex = 0; remainderIndex < remainder; remainderIndex++)
        {
            totalAlloyHarassment += alloyMinedPerSecondByWorker * timeToProduceWorker * (simultaneousProductionFloor + 1);
        }

        for (var travelTimeIndex = 0; travelTimeIndex < numberOfWorkersLostToHarass; travelTimeIndex++)
        {
            var townHallIndex = travelTimeIndex % travelTimes.Count;
            totalAlloyHarassment += alloyMinedPerSecondByWorker * travelTimes[townHallIndex];
        }

        return totalAlloyHarassment;
    }

    protected override void OnInitialized()
    {
        base.OnInitialized();
        Calculate();
    }


    public List<TravelTime> TravelTimes { get; set; } = new() { new TravelTime(0, 0) };

    private void OnTownHallsChanged(ChangeEventArgs obj)
    {
        NumberOfTownHallsExisting = int.Parse(obj.Value!.ToString()!);

        while (TravelTimes.Count > NumberOfTownHallsExisting)
            TravelTimes.Remove(TravelTimes.Last());

        while (TravelTimes.Count < NumberOfTownHallsExisting)
            TravelTimes.Add(new TravelTime(TravelTimes.Count, 10 * TravelTimes.Count));
        Calculate();
    }

    private void OnTownHallTravelTimeChanged(ChangeEventArgs obj, TravelTime travelTime)
    {
        travelTime.Value = (int)obj.Value!;

        Calculate();
        StateHasChanged();
    }

}