package de.szut.casino.slots;

import de.szut.casino.shared.service.BalanceService;
import de.szut.casino.user.UserEntity;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;

import static de.szut.casino.slots.Symbol.*;

@Service
public class SlotService {
    private final int REEL_LENGTH = 32;

    private final List<Symbol> firstReel;
    private final List<Symbol> secondReel;
    private final List<Symbol> thirdReel;

    private final Random random;
    private final BalanceService balanceService;

    public SlotService(BalanceService balanceService) {
        this.random = new Random();
        this.balanceService = balanceService;

        List<Symbol> reelStrip = createReelStrip();
        this.firstReel = shuffleReel(reelStrip);
        this.secondReel = shuffleReel(reelStrip);
        this.thirdReel = shuffleReel(reelStrip);
    }

    public SpinResult spin(BigDecimal betAmount, UserEntity user) {
        int index1 = this.random.nextInt(REEL_LENGTH);
        int index2 = this.random.nextInt(REEL_LENGTH);
        int index3 = this.random.nextInt(REEL_LENGTH);

        Symbol symbol1 = getSymbolAt(this.firstReel, index1);
        Symbol symbol2 = getSymbolAt(this.secondReel, index2);
        Symbol symbol3 = getSymbolAt(this.thirdReel, index3);

        Status status = determineStatus(symbol1, symbol2, symbol3);

        SpinResult spinResult = processResult(betAmount, user, status, symbol1);
        buildResultMatrix(spinResult, index1, index2, index3);

        return spinResult;
    }

    private SpinResult processResult(BigDecimal betAmount, UserEntity user, Status status, Symbol winSymbol) {
        SpinResult spinResult = new SpinResult();
        spinResult.setStatus(status.name().toLowerCase());

        this.balanceService.subtractFunds(user, betAmount);

        if (status == Status.WIN) {
            BigDecimal winAmount = betAmount.multiply(winSymbol.getPayoutMultiplier());
            this.balanceService.addFunds(user, winAmount);
            spinResult.setAmount(winAmount);
        } else {
            spinResult.setAmount(betAmount);
        }

        return spinResult;
    }

    private void buildResultMatrix(SpinResult spinResult, int index1, int index2, int index3) {
        List<List<Symbol>> resultMatrix = new ArrayList<>(3);

        for (int i = 0; i < 3; i++) {
            resultMatrix.add(new ArrayList<>(3));
        }

        resultMatrix.getFirst().add(getSymbolAt(this.firstReel, index1 - 1));
        resultMatrix.getFirst().add(getSymbolAt(this.secondReel, index2 - 1));
        resultMatrix.getFirst().add(getSymbolAt(this.thirdReel, index3 - 1));

        resultMatrix.get(1).add(getSymbolAt(this.firstReel, index1));
        resultMatrix.get(1).add(getSymbolAt(this.secondReel, index2));
        resultMatrix.get(1).add(getSymbolAt(this.thirdReel, index3));

        resultMatrix.getLast().add(getSymbolAt(this.firstReel, index1 + 1));
        resultMatrix.getLast().add(getSymbolAt(this.secondReel, index2 + 1));
        resultMatrix.getLast().add(getSymbolAt(this.thirdReel, index3 + 1));

        spinResult.setResultMatrix(resultMatrix);
    }

    private List<Symbol> shuffleReel(List<Symbol> reelStrip) {
        Collections.shuffle(reelStrip, this.random);

        return reelStrip;
    }

    private List<Symbol> createReelStrip() {
        List<Symbol> reelStrip = new ArrayList<>(REEL_LENGTH);
        addSymbolsToStrip(reelStrip, CHERRY, CHERRY.getCountPerStrip());
        addSymbolsToStrip(reelStrip, BELL, BELL.getCountPerStrip());
        addSymbolsToStrip(reelStrip, BAR, BAR.getCountPerStrip());
        addSymbolsToStrip(reelStrip, SEVEN, SEVEN.getCountPerStrip());
        addSymbolsToStrip(reelStrip, BLANK, BLANK.getCountPerStrip());
        return reelStrip;
    }

    private void addSymbolsToStrip(List<Symbol> strip, Symbol symbol, int count) {
        for (int i = 0; i < count; i++) {
            strip.add(symbol);
        }
    }

    private Symbol getSymbolAt(List<Symbol> reel, int index) {
        int effectiveIndex = index % REEL_LENGTH;

        if (effectiveIndex < 0) {
            effectiveIndex += REEL_LENGTH;
        }

        return reel.get(effectiveIndex);
    }

    private Status determineStatus(Symbol symbol1, Symbol symbol2, Symbol symbol3) {
        boolean allSymbolsMatch = symbol1.equals(symbol2) && symbol1.equals(symbol3);

        if (allSymbolsMatch) {
            if (symbol1 == Symbol.BLANK) {
                return Status.BLANK;
            } else {
                return Status.WIN;
            }
        }

        return Status.LOSE;
    }
}
