//
//  GameScene.swift
//  Privyet
//
//  Created by Amy Bowersox on 5/23/20.
//  Copyright © 2020 Erbosoft Metaverse Design Solutions. All rights reserved.
//

import SpriteKit
import GameplayKit

let BlockSize: CGFloat = 20.0

let TickLengthLevelOne = TimeInterval(600)

class GameScene: SKScene {
    let gameLayer = SKNode()
    let shapeLayer = SKNode()
    let LayerPosition = CGPoint(x: 6, y: -6)
    
    var tick: (() -> ())?
    var tickLengthMillis = TickLengthLevelOne
    var lastTick: NSDate?
    
    var scaleFactor: CGFloat!
    
    var textureCache = Dictionary<String, SKTexture>()
    var textureAtlas: SKTextureAtlas?
    
    required init(coder aDecoder: NSCoder) {
        fatalError("NSCoder not supported")
    }

    override init(size: CGSize) {
        super.init(size: size)
        //print("Init scene with size \(size)")
        
        textureAtlas = SKTextureAtlas(named: "Sprites")
        
        anchorPoint = CGPoint(x: 0, y: 1.0)
        
        let background = SKSpriteNode(imageNamed: "background")
        //print("Background is sized \(background.size)")
        
        // Scale computations
        scaleFactor = CGFloat(min(size.width / background.size.width, size.height / background.size.height))
        
        background.position = CGPoint(x: 0, y: 0)
        background.anchorPoint = CGPoint(x: 0, y: 1.0)
        background.setScale(scaleFactor)
        addChild(background)
        
        gameLayer.setScale(scaleFactor)
        addChild(gameLayer)
        
        // Load and add the game board and shape layer
        let gameBoardTexture = SKTexture(imageNamed: "gameboard")
        let gameBoard = SKSpriteNode(texture: gameBoardTexture, size: CGSize(width: BlockSize * CGFloat(NumColumns), height: BlockSize * CGFloat(NumRows)))
        gameBoard.anchorPoint = CGPoint(x: 0, y: 1.0)
        gameBoard.position = LayerPosition
        shapeLayer.position = LayerPosition
        shapeLayer.addChild(gameBoard)
        gameLayer.addChild(shapeLayer)
        
        // Set the theme music to play infinitely.
        run(SKAction.repeatForever(SKAction.playSoundFileNamed("Sounds/theme.mp3", waitForCompletion: true)))
    }
    
    // Convenience function to play a sound file.
    func playSound(sound: String) {
        run(SKAction.playSoundFileNamed(sound, waitForCompletion: false))
    }

    // Called once per frame by the framework, before it's rendered.
    override func update(_ currentTime: TimeInterval) {
        guard let lastTick = lastTick else {
            return
        }
        let timePassed = lastTick.timeIntervalSinceNow * -1000.0
        if timePassed > tickLengthMillis {
            self.lastTick = NSDate()
            tick?()
        }
    }
    
    // Starts the game tick going.
    func startTicking() {
        lastTick = NSDate()
    }
    
    // Stops the game tick.
    func stopTicking() {
        lastTick = nil
    }
    
    // Convert bucket row/column coordinates to display coordinates.
    func pointForColumn(column: Int, row: Int) -> CGPoint {
        let x = LayerPosition.x + (CGFloat(column) * BlockSize) + (BlockSize / 2)
        let y = LayerPosition.y - ((CGFloat(row) * BlockSize) + (BlockSize / 2))
        return CGPoint(x: x, y: y)
    }
    
    func rectForBucket() -> CGRect {
        let width = CGFloat(NumColumns) * BlockSize
        let height = CGFloat(NumRows) * BlockSize
        return CGRect(x: (LayerPosition.x + (BlockSize / 2)) * scaleFactor, y: (LayerPosition.y + (BlockSize / 2)) * scaleFactor, width: width * scaleFactor, height: height * scaleFactor)
    }
    
    // Add the "preview" shape to the current scene.  This also "rezzes in" a shape's block sprites.
    func addPreviewShapeToScene(shape: Shape, completion: @escaping () -> ()) {
        for block in shape.blocks {
            var texture = textureCache[block.spriteName]
            if texture == nil {
                texture = textureAtlas?.textureNamed(block.spriteName)
                textureCache[block.spriteName] = texture
            }
            let sprite = SKSpriteNode(texture: texture)
            
            sprite.position = pointForColumn(column: block.column, row: block.row - 2)
            shapeLayer.addChild(sprite)
            block.sprite = sprite
            
            // Animation
            sprite.alpha = 0
            
            let moveAction = SKAction.move(to: pointForColumn(column: block.column, row: block.row), duration: TimeInterval(0.2))
            moveAction.timingMode = .easeOut
            let fadeInAction = SKAction.fadeAlpha(to: 0.7, duration: 0.4)
            fadeInAction.timingMode = .easeOut
            sprite.run(SKAction.group([moveAction, fadeInAction]))
        }
        let waitAction = SKAction.wait(forDuration: 0.4)
        let completeAction = SKAction.run(completion)
        run(SKAction.sequence([waitAction, completeAction]))
    }
    
    // Move the "preview" shape into position so it becomes the new "falling" shape.
    func movePreviewShape(shape: Shape, completion: @escaping () -> ()) {
        for block in shape.blocks {
            let sprite = block.sprite!
            let moveTo = pointForColumn(column: block.column, row: block.row)
            let moveToAction: SKAction = SKAction.move(to: moveTo, duration: 0.4)
            moveToAction.timingMode = .easeOut
            sprite.run(SKAction.group([moveToAction, SKAction.fadeAlpha(to: 1.0, duration: 0.2)]))
        }
        let waitAction = SKAction.wait(forDuration: 0.2)
        let completeAction = SKAction.run(completion)
        run(SKAction.sequence([waitAction, completeAction]))
    }
    
    // Redraw the falling shape by shifting the position of its component blocks.
    func redrawShape(shape: Shape, completion: @escaping () -> ()) {
        for block in shape.blocks {
            let sprite = block.sprite!
            let moveTo = pointForColumn(column: block.column, row: block.row)
            let moveToAction: SKAction = SKAction.move(to: moveTo, duration: 0.05)
            moveToAction.timingMode = .easeOut
            sprite.run(moveToAction)
            if block == shape.blocks.last {
                sprite.run(SKAction.run(completion))
            }
        }
    }
    
    // Animate the removed lines "exploding" and the remaining blocks dropping into place.
    func animateCollapsingLines(linesToRemove: Array<Array<Block>>, fallenBlocks: Array<Array<Block>>, completion: @escaping () -> ()) {
        var longestDuration: TimeInterval = 0
        
        // Animate the falling blocks dropping into place
        for (columnIdx, column) in fallenBlocks.enumerated() {
            for (blockIdx, block) in column.enumerated() {
                let newPosition = pointForColumn(column: block.column, row: block.row)
                let sprite = block.sprite!
                
                let delay = (TimeInterval(columnIdx) * 0.05) + (TimeInterval(blockIdx) * 0.05)
                let duration = TimeInterval(((sprite.position.y - newPosition.y) / BlockSize) * 0.1)
                let moveAction = SKAction.move(to: newPosition, duration: duration)
                moveAction.timingMode = .easeOut
                sprite.run(SKAction.sequence([SKAction.wait(forDuration: delay), moveAction]))
                longestDuration = max(longestDuration, duration + delay)
            }
        }
        
        // Animate the "removed" blocks "exploding"
        for rowToRemove in linesToRemove {
            for block in rowToRemove {
                let randomRadius = CGFloat(Float.random(in: 100.0 ... 500.0))
                let goLeft = Bool.random()
                
                var point = pointForColumn(column: block.column, row: block.row)
                point = CGPoint(x: point.x + (goLeft ? -randomRadius : randomRadius), y: point.y)
                
                let randomDuration = TimeInterval(Float.random(in: 0.5 ... 2.5))
                
                var startAngle = CGFloat(Double.pi)
                var endAngle = startAngle * 2
                if (goLeft) {
                    endAngle = startAngle
                    startAngle = 0
                }
                
                let archPath = UIBezierPath(arcCenter: point, radius: randomRadius, startAngle: startAngle, endAngle: endAngle, clockwise: goLeft)
                
                let archAction = SKAction.follow(archPath.cgPath, asOffset: false, orientToPath: true, duration: randomDuration)
                archAction.timingMode = .easeIn
                let sprite = block.sprite!
                sprite.zPosition = 100
                sprite.run(SKAction.sequence([
                    SKAction.group([archAction, SKAction.fadeOut(withDuration: TimeInterval(randomDuration))]),
                    SKAction.removeFromParent()]))
            }
        }
        run(SKAction.sequence([SKAction.wait(forDuration: longestDuration), SKAction.run(completion)]))
    }
}
