import SwiftUI
import RealityKit
import ARKit
import AVFoundation

var cardEntity: ModelEntity! 
var cardEntity2: ModelEntity!
var playerCardEntities: [ModelEntity] = []

var dealerCardEntity: ModelEntity!
var dealerCardEntity2: ModelEntity!
var dealerCardEntities: [ModelEntity] = []

var cardMesh = MeshResource.generateBox(
    width: 0.063,
    height: 0.001,
    depth: 0.088,
    cornerRadius: 0,
    splitFaces: true
)

let anchor = AnchorEntity(plane: .horizontal)

struct BlackjackARGameView: View {
    @EnvironmentObject var gameState: GameState
    
    @State private var cardFlipAudio: AVAudioPlayer?
    
    @State private var betIsZero = true
    @State private var bet = 0
    @State private var notEnoughMoney = false
    @State private var playing = false
    @State private var canDoubleDown = false
    @State private var playerStands = false
    @State private var playerCards = ["", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ""]
    @State private var currentPlayedCards = 2
    @State private var dealerCards = ["", "", "", "", "", "", ""]
    @State private var currentDealedCards = 2
    @State private var dealerSum = 0
    @State private var playerSum = 0
    @State private var dealerStands = false
    @State private var playerBust = false
    @State private var dealerBust = false
    @State private var playerHasBlackjack = false
    @State private var playerHas11Ace = false
    @State private var dealerHas11Ace = false
    @State private var cardValue = 0
    
    let purpleColor = UIColor(red: 0.47, green: 0.0, blue: 0.43, alpha: 1.0)
    
    let cardSuites = ["Hearts", "Spades", "Diamonds", "Clubs"]
    let cardNames = ["Ace", "2", "3", "4", "5", "6", "7", "8", "9", "10", "Jack", "Queen", "King"]
    
    func initializeSounds() {
        guard let url = Bundle.main.url(forResource: "Flip Card SFX", withExtension: "mp3") else {
            return
        }
        
        cardFlipAudio = try? AVAudioPlayer(contentsOf: url)
    }
    
    func flipCard(_ card: ModelEntity) {
        var transform = card.transform
        let angle = Float.pi
        transform.rotation = simd_quatf(angle: angle, axis: [0, 0, 1])
        
        card.move(to: transform, relativeTo: card.parent, duration: 0.5, timingFunction: .easeInOut)
        
        cardFlipAudio?.stop()
        cardFlipAudio?.currentTime = 0
        cardFlipAudio?.play()
    }
    
    func resetCard(_ card: ModelEntity) {
        var transform = card.transform
        let angle = Float(0)
        transform.rotation = simd_quatf(angle: angle, axis: [0, 0, 1])
        
        card.move(to: transform, relativeTo: card.parent, duration: 0.1, timingFunction: .easeInOut)
    }
    
    func changeCardFace(from name: String) -> [RealityKit.Material] {
        let frontTexture = try! TextureResource.load(named: name)
        var frontMat = UnlitMaterial()
        frontMat.color = . init(texture: .init(frontTexture))
        
        let backTexture = try! TextureResource.load(named: "Card Back")
        
        var backMat = UnlitMaterial()
        backMat.color = .init(texture: .init(backTexture))
        
        var sideMat = UnlitMaterial()
        sideMat.color = .init(tint: purpleColor)
        
        let materials: [RealityKit.Material] = [
            sideMat,
            backMat,
            sideMat,
            frontMat,
            sideMat,
            sideMat
        ]
        
        return materials
    }
    
    func hitCard() -> Void {
        let frontTexture = try! TextureResource.load(named: playerCards[currentPlayedCards])
        let backTexture = try! TextureResource.load(named: "Card Back")
        
        var frontMat = UnlitMaterial()
        frontMat.color = . init(texture: .init(frontTexture))
        
        var backMat = UnlitMaterial()
        backMat.color = .init(texture: .init(backTexture))
        
        var sideMat = UnlitMaterial()
        sideMat.color = .init(tint: purpleColor)
        
        let materials: [RealityKit.Material] = [
            sideMat,
            backMat,
            sideMat,
            frontMat,
            sideMat,
            sideMat
        ]
        
        let cardEntityTemporal = ModelEntity(mesh: cardMesh, materials: materials)
        
        let x = (Float(currentPlayedCards) * 0.02) - 0.01
        let y = Float(currentPlayedCards) * 0.01
        let z = Float(currentPlayedCards) * -0.02
        
        cardEntityTemporal.position = [x, y, z]
        
        playerCardEntities.append(cardEntityTemporal)
        
        anchor.addChild(playerCardEntities[currentPlayedCards - 2])
    }
    
    func dealCard() -> Void {
        let frontTexture = try! TextureResource.load(named: dealerCards[currentDealedCards])
        let backTexture = try! TextureResource.load(named: "Card Back")
        
        var frontMat = UnlitMaterial()
        frontMat.color = . init(texture: .init(frontTexture))
        
        var backMat = UnlitMaterial()
        backMat.color = .init(texture: .init(backTexture))
        
        var sideMat = UnlitMaterial()
        sideMat.color = .init(tint: purpleColor)
        
        let materials: [RealityKit.Material] = [
            sideMat,
            backMat,
            sideMat,
            frontMat,
            sideMat,
            sideMat
        ]
        
        let cardEntityTemporal = ModelEntity(mesh: cardMesh, materials: materials)
        
        let x = (Float(currentDealedCards) * -0.07) + 0.035
        let y = Float(0)
        let z = Float(-0.3)
        
        cardEntityTemporal.position = [x, y, z]
        
        dealerCardEntities.append(cardEntityTemporal)
        
        anchor.addChild(dealerCardEntities[currentDealedCards - 2])
    }
    
    func generateRandomCard() -> String {
        return cardNames[Int.random(in: 0...12)] + " of " + cardSuites[Int.random(in: 0...3)]
    }
    
    func readCardValue(from string: String) -> Int {
        switch string.first {
        case "A":
            return 1
        case "2":
            return 2
        case "3":
            return 3
        case "4":
            return 4
        case "5":
            return 5
        case "6":
            return 6
        case "7":
            return 7
        case "8":
            return 8
        case "9":
            return 9
        case "1":
            return 10
        case "J":
            return 10
        case "Q":
            return 10
        case "K":
            return 10
        case .none:
            return -1
        default:
            return -1
        }
    }
    
    func calculateValidNextValue(from int: Int, from sum: Int) -> Int {
        if int == 1 && sum <= 10 {
            return 11
        } else {
            return int
        }
    }
    
    func dealDealerCards() -> Void {
        var expectedSum = dealerSum
        var cardsToDealDealer = 0
        var dealerHad11Ace = dealerHas11Ace
        
        for i in 2...7 {            
            if expectedSum >= 17 {
                cardsToDealDealer = i - 2
                
                if i == 2 {
                    dealerStands = true
                    
                    if playerSum > dealerSum {
                        bet *= 2
                    } else if dealerSum > playerSum {
                        bet = 0
                    }
                }
                break
            }
            
            cardValue = calculateValidNextValue(from: readCardValue(from: dealerCards[i]), from: expectedSum)
            
            if expectedSum + cardValue > 21 && dealerHas11Ace {
                expectedSum -= 10
                dealerHas11Ace = false
            }
            
            if cardValue == 11 {
                dealerHas11Ace = true
            }
            
            expectedSum += cardValue
        }
        
        for i in 0..<cardsToDealDealer {
            DispatchQueue.main.asyncAfter(deadline: .now() + Double(i + 1)) {
                cardValue = calculateValidNextValue(from: readCardValue(from: dealerCards[currentDealedCards]), from: dealerSum)
                
                dealCard()
                flipCard(dealerCardEntities[currentDealedCards - 2])
                
                currentDealedCards += 1
                
                if dealerSum + cardValue > 21 && dealerHad11Ace {
                    dealerSum -= 10
                    dealerHad11Ace = false
                }
                
                if cardValue == 11 {
                    dealerHad11Ace = true
                }
                
                dealerSum += cardValue
                
                if dealerSum > 21 {
                    dealerBust = true
                    bet *= 2
                }
                
                if dealerSum >= 17 {
                    dealerStands = true
                    
                    if playerSum > dealerSum {
                        bet *= 2
                    } else if dealerSum > playerSum && dealerSum <= 21 {
                        bet = 0
                    }
                }
            }
        }
    }
    
    var body: some View {
        ZStack {
            ARViewContainer().edgesIgnoringSafeArea(.all)
            
            VStack {
                Text("\n")
                
                Text("$\(gameState.money)")
                    .foregroundColor(.black)
                    .font(.system(.title, design: .rounded)).bold()
                    .padding(10)
                    .background(Color.yellow)
                    .cornerRadius(10)
                    .padding(.bottom, 50)
                
                Text("\n\n\n")
                
                Spacer()
                
                if !playing {
                    Text("Dealer\n\nYour cards")
                        .multilineTextAlignment(.center)
                        .font(.system(.title2, design: .rounded)).bold()
                        .foregroundColor(.white)
                } else {
                    Text("Dealer - \(dealerSum)\n\nYour cards - \(playerSum)")
                        .multilineTextAlignment(.center)
                        .font(.system(.title2, design: .rounded)).bold()
                        .foregroundColor(.white)
                }
                
                Spacer()
                
                Text("\n\n\n\n")
                
                HStack {
                    if !playing {
                        Button {
                            bet -= 10
                        } label: {
                            Text(" -  ")
                                .foregroundColor(.white)
                                .font(.system(.title3, design: .rounded)).bold()
                                .padding(5)
                                .background(Color.brown)
                                .cornerRadius(15)
                        }
                        .disabled(betIsZero)
                    }
                    
                    if dealerStands || playerBust || playerHasBlackjack {
                        if playerHasBlackjack {
                            Text("Blackjack! - You win +$\(bet)")
                                .foregroundColor(.white)
                                .font(.system(.title2, design: .rounded)).bold()
                        } else if playerBust {
                            Text("Player bust! - You lose")
                                .foregroundColor(.white)
                                .font(.system(.title2, design: .rounded)).bold()
                        } else if dealerBust {
                            Text("Dealer bust! - You win +$\(bet)")
                                .foregroundColor(.white)
                                .font(.system(.title2, design: .rounded)).bold()
                        } else {
                            if dealerSum > playerSum {
                                Text("Dealer won! - You lose")
                                    .foregroundColor(.white)
                                    .font(.system(.title2, design: .rounded)).bold()
                            } else if playerSum > dealerSum {
                                Text("Player won! - You win +$\(bet)")
                                    .foregroundColor(.white)
                                    .font(.system(.title2, design: .rounded)).bold()
                            } else {
                                Text("Push! - You win +$\(bet)")
                                    .foregroundColor(.white)
                                    .font(.system(.title2, design: .rounded)).bold()
                            }
                        }
                    } else {
                        Text("$\(bet)")
                            .foregroundColor(.white)
                            .font(.system(.title2, design: .rounded)).bold()
                    }
                    
                    if !playing {
                        Button {
                            bet += 10
                        } label: {
                            Text(" +  ")
                                .foregroundColor(.white)
                                .font(.system(.title3, design: .rounded)).bold()
                                .padding(5)
                                .background(Color.brown)
                                .cornerRadius(15)
                        }
                        .disabled(notEnoughMoney)
                        
                        .onChange(of: bet) {
                            betIsZero = bet == 0
                            notEnoughMoney = (bet + 10) > gameState.money
                            canDoubleDown = (bet * 2) <= gameState.money
                        }
                    }
                }
                
                Text("\n")
                
                if !playing {
                    Button {
                        gameState.money -= bet
                        playing = true
                        
                        for i in 0...21 {
                            playerCards[i] = generateRandomCard()
                        }
                        for i in 0...6 {
                            dealerCards[i] = generateRandomCard()
                        }
                        
                        cardEntity?.model?.materials = changeCardFace(from: playerCards[0])
                        cardEntity2?.model?.materials = changeCardFace(from: playerCards[1])
                        
                        flipCard(cardEntity)
                        flipCard(cardEntity2)
                        
                        dealerCardEntity?.model?.materials = changeCardFace(from: dealerCards[0])
                        
                        flipCard(dealerCardEntity)
                        
                        dealerSum = 0
                        playerSum = 0
                        
                        cardValue = calculateValidNextValue(from: readCardValue(from: dealerCards[0]), from: dealerSum)
                        
                        if cardValue == 11 {
                            dealerHas11Ace = true
                        }
                        
                        dealerSum += cardValue
                        
                        cardValue = calculateValidNextValue(from: readCardValue(from: playerCards[0]), from: playerSum)
                        
                        if cardValue == 11 {
                            playerHas11Ace = true
                        }
                        
                        playerSum += cardValue
                        
                        cardValue = calculateValidNextValue(from: readCardValue(from: playerCards[1]), from: playerSum)
                        
                        if cardValue == 11 {
                            playerHas11Ace = true
                        }
                        
                        playerSum += cardValue
                        
                        if playerSum == 21 {
                            playerHasBlackjack = true
                            bet *= 3
                            
                            dealerSum += calculateValidNextValue(from: readCardValue(from: dealerCards[1]), from: dealerSum)
                            
                            flipCard(dealerCardEntity2)
                            
                            dealerCardEntity2?.model?.materials = changeCardFace(from: dealerCards[1])
                        }
                        
                    } label: {
                        Text("Bet")
                            .foregroundColor(.black)
                            .font(.system(.title2, design: .rounded)).bold()
                            .padding(10)
                            .background(Color.orange)
                            .cornerRadius(20)
                    }
                    .disabled(betIsZero)
                } else {
                    if dealerStands || playerBust || playerHasBlackjack {
                        HStack {
                            Button {
                                for i in 0..<currentPlayedCards - 2 {
                                    anchor.removeChild(playerCardEntities[i])
                                }
                                playerCardEntities.removeAll()
                                
                                for i in 0..<currentDealedCards - 2 {
                                    anchor.removeChild(dealerCardEntities[i])
                                }
                                dealerCardEntities.removeAll()
                                
                                resetCard(cardEntity)
                                resetCard(cardEntity2)
                                
                                resetCard(dealerCardEntity)
                                resetCard(dealerCardEntity2)
                                
                                gameState.money += bet
                                dealerStands = false
                                dealerBust = false
                                playerBust = false
                                currentPlayedCards = 2
                                currentDealedCards = 2
                                playerStands = false
                                canDoubleDown = false
                                playing = false
                                betIsZero = true
                                bet = 0
                                
                                if (gameState.money < 10) {
                                    notEnoughMoney = true
                                } else {
                                    notEnoughMoney = false
                                }
                                
                                playerHasBlackjack = false
                                playerHas11Ace = false
                                dealerHas11Ace = false
                                
                                cardEntity?.model?.materials = changeCardFace(from: "Card Back")
                                cardEntity2?.model?.materials = changeCardFace(from: "Card Back")
                                
                                dealerCardEntity?.model?.materials = changeCardFace(from: "Card Back")
                                dealerCardEntity2?.model?.materials = changeCardFace(from: "Card Back")
                                
                            } label: {
                                Text("Continue")
                                    .foregroundColor(.black)
                                    .font(.system(.title2, design: .rounded)).bold()
                                    .padding(10)
                                    .background(Color.red)
                                    .cornerRadius(20)
                            }
                        }
                    } else {
                        HStack {
                            Button {
                                playerStands = true
                                
                                cardValue = calculateValidNextValue(from: readCardValue(from: dealerCards[1]), from: dealerSum)
                                
                                if cardValue == 11 {
                                    dealerHas11Ace = true
                                }
                                
                                flipCard(dealerCardEntity2)
                                
                                dealerSum += cardValue
                                
                                dealerCardEntity2?.model?.materials = changeCardFace(from: dealerCards[1])
                                
                                dealDealerCards()
                                
                            } label: {
                                Text("Stand")
                                    .foregroundColor(.black)
                                    .font(.system(.title2, design: .rounded)).bold()
                                    .padding(10)
                                    .background(Color.orange)
                                    .cornerRadius(20)
                            }
                            .disabled(playerStands || playerBust)
                            
                            Button {
                                canDoubleDown = false
                                
                                cardValue = calculateValidNextValue(from: readCardValue(from: playerCards[currentPlayedCards]), from: playerSum)
                                
                                if playerSum + cardValue > 21 && playerHas11Ace {
                                    playerSum -= 10
                                    playerHas11Ace = false
                                }
                                
                                if cardValue == 11 {
                                    playerHas11Ace = true
                                }
                                
                                playerSum += cardValue
                                
                                hitCard()
                                flipCard(playerCardEntities[currentPlayedCards - 2])
                                
                                currentPlayedCards += 1
                                
                                if playerSum > 21 {
                                    playerBust = true
                                    bet = 0
                                    
                                    dealerSum += calculateValidNextValue(from: readCardValue(from: dealerCards[1]), from: dealerSum)
                                    
                                    flipCard(dealerCardEntity2)
                                    
                                    dealerCardEntity2?.model?.materials = changeCardFace(from: dealerCards[1])
                                }
                                
                            } label: {
                                Text("Hit")
                                    .foregroundColor(.black)
                                    .font(.system(.title2, design: .rounded)).bold()
                                    .padding(10)
                                    .padding(.horizontal, 20)
                                    .background(Color.green)
                                    .cornerRadius(20)
                            }
                            .disabled(playerStands || playerBust)
                            
                            Button {
                                gameState.money -= bet
                                bet = bet * 2
                                playerStands = true
                                
                                cardValue = calculateValidNextValue(from: readCardValue(from: playerCards[currentPlayedCards]), from: playerSum)
                                
                                if playerSum + cardValue > 21 && playerHas11Ace {
                                    playerSum -= 10
                                    playerHas11Ace = false
                                }
                                
                                if cardValue == 11 {
                                    playerHas11Ace = true
                                }
                                
                                playerSum += cardValue
                                
                                hitCard()
                                flipCard(playerCardEntities[currentPlayedCards - 2])
                                
                                currentPlayedCards += 1
                                
                                if playerSum > 21 {
                                    playerBust = true
                                    bet = 0
                                    
                                    dealerSum += calculateValidNextValue(from: readCardValue(from: dealerCards[1]), from: dealerSum)
                                    
                                    flipCard(dealerCardEntity2)
                                    
                                    dealerCardEntity2?.model?.materials = changeCardFace(from: dealerCards[1])
                                } else {                                
                                    cardValue = calculateValidNextValue(from: readCardValue(from: dealerCards[1]), from: dealerSum)
                                    
                                    if cardValue == 11 {
                                        dealerHas11Ace = true
                                    }
                                    
                                    flipCard(dealerCardEntity2)
                                    
                                    dealerSum += cardValue
                                    
                                    dealerCardEntity2?.model?.materials = changeCardFace(from: dealerCards[1])
                                    
                                    dealDealerCards()
                                }
                                
                            } label: {
                                Text("Double down")
                                    .foregroundColor(.black)
                                    .font(.system(.title2, design: .rounded)).bold()
                                    .padding(10)
                                    .background(Color.red)
                                    .cornerRadius(20)
                            }
                            .disabled(!canDoubleDown || playerStands || playerBust)
                        }
                    }
                }
                
                Text("\n")
            }
        }
        .onDisappear {
            for i in 0..<currentPlayedCards - 2 {
                anchor.removeChild(playerCardEntities[i])
            }
            playerCardEntities.removeAll()
            
            for i in 0..<currentDealedCards - 2 {
                anchor.removeChild(dealerCardEntities[i])
            }
            dealerCardEntities.removeAll()
            
            cardEntity?.model?.materials = changeCardFace(from: "Card Back")
            cardEntity2?.model?.materials = changeCardFace(from: "Card Back")
            
            dealerCardEntity?.model?.materials = changeCardFace(from: "Card Back")
            dealerCardEntity2?.model?.materials = changeCardFace(from: "Card Back")
            
            anchor.children.removeAll()
        }
        .onDisappear() {
            cardFlipAudio?.stop()
            cardFlipAudio?.currentTime = 0
        }
        .onAppear() {
            if (gameState.money < 10) {
                notEnoughMoney = true
            } else {
                notEnoughMoney = false
            }
            
            initializeSounds()
        }
    }
}

struct ARViewContainer: UIViewRepresentable {
    let purpleColor = UIColor(red: 0.47, green: 0.0, blue: 0.43, alpha: 1.0)
    
    func makeUIView(context: Context) -> ARView {
        let arView = ARView(frame: .zero)
        
        let config = ARWorldTrackingConfiguration()
        config.planeDetection = [.horizontal]
        arView.session.run(config)
        
        let backTexture = try! TextureResource.load(named: "Card Back")
        
        var backMat = UnlitMaterial()
        backMat.color = .init(texture: .init(backTexture))
        
        var sideMat = UnlitMaterial()
        sideMat.color = .init(tint: purpleColor)
        
        let materials: [RealityKit.Material] = [
            sideMat,
            backMat,
            sideMat,
            backMat,
            sideMat,
            sideMat
        ]
        
        cardEntity = ModelEntity(mesh: cardMesh, materials: materials)
        cardEntity.position = [-0.01, 0, 0]
        
        anchor.addChild(cardEntity)
        
        cardEntity2 = ModelEntity(mesh: cardMesh, materials: materials)
        cardEntity2.position = [0.01, 0.01, -0.02]
        
        anchor.addChild(cardEntity2)
        
        dealerCardEntity = ModelEntity(mesh: cardMesh, materials: materials)
        dealerCardEntity.position = [-0.035, 0, -0.3]
        
        anchor.addChild(dealerCardEntity)
        
        dealerCardEntity2 = ModelEntity(mesh: cardMesh, materials: materials)
        dealerCardEntity2.position = [0.035, 0, -0.3]
        
        anchor.addChild(dealerCardEntity2)
        
        arView.scene.anchors.append(anchor)
        
        return arView
    }
    
    func updateUIView(_ uiView: ARView, context: Context) {}
}

#Preview {
    BlackjackARGameView()
        .environmentObject(GameState())
}

