package uk.co.rossbinden.transferencevoid.screen

import com.badlogic.ashley.core.Engine
import com.badlogic.ashley.core.Entity
import com.badlogic.gdx.ScreenAdapter
import com.badlogic.gdx.audio.Music
import com.badlogic.gdx.audio.Sound
import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.graphics.g2d.BitmapFont
import com.badlogic.gdx.graphics.g2d.GlyphLayout
import com.badlogic.gdx.graphics.g2d.TextureAtlas
import com.badlogic.gdx.maps.tiled.TiledMap
import com.badlogic.gdx.maps.tiled.renderers.OrthogonalTiledMapRenderer
import com.badlogic.gdx.physics.box2d.*
import com.badlogic.gdx.utils.Timer
import uk.co.rossbinden.transferencevoid.TransferenceVoid.assetManager
import uk.co.rossbinden.transferencevoid.component.*
import uk.co.rossbinden.transferencevoid.system.PhysicsSystem
import uk.co.rossbinden.transferencevoid.system.RenderSystem
import uk.co.rossbinden.transferencevoid.system.TerminalDataSystem
import uk.co.rossbinden.transferencevoid.system.TimerSystem
import java.nio.ByteBuffer
import java.util.*

object MainScreen: ScreenAdapter() {

    val engine = Engine()
    var init = false

    init {
        // Systems
        val renderSystem = RenderSystem(1280f, 720f)
        engine.addSystem(renderSystem)

        val physicsSystem = PhysicsSystem()
        engine.addSystem(physicsSystem)

        val timerSystem = TimerSystem()
        engine.addSystem(timerSystem)

        val terminalDataSystem = TerminalDataSystem()
        engine.addSystem(terminalDataSystem)

        assetManager.load("textures/textures.atlas", TextureAtlas::class.java)
        assetManager.load("maps/one.tmx", TiledMap::class.java)
        assetManager.load("sounds/Download.wav", Sound::class.java)
        assetManager.load("sounds/Transfer.wav", Sound::class.java)
        assetManager.load("sounds/Upload.wav", Sound::class.java)
        assetManager.load("fonts/envy_code_r_32.fnt", BitmapFont::class.java)
        assetManager.load("music/Transference Void Music 1 min.ogg", Music::class.java)
    }

    fun createPlayer(): Entity {
        val physicsSystem = engine.getSystem(PhysicsSystem::class.java)

        val player = Entity()

        val spawn = engine.getEntitiesFor(SPAWN_FAMILY).map { entity -> SPAWN.get(entity) }.sortedBy { it.spawns }.firstOrNull()
        if (spawn != null) {
            spawn.spawns++
        }
        val bodyDef = BodyDef()
        bodyDef.type = BodyDef.BodyType.DynamicBody
        bodyDef.position.set(spawn?.x?:0f, spawn?.y?:0f)
        val polygonShape = PolygonShape()
        polygonShape.set(
                floatArrayOf(
                        -0.5f, -0.5f,
                        0.5f, 0f,
                        -0.5f, 0.5f
                )
        )
        val fixtureDef = FixtureDef()
        fixtureDef.shape = polygonShape
        fixtureDef.density = 1f
        fixtureDef.friction = 0f
        fixtureDef.restitution = 0f
        val body = physicsSystem.world.createBody(bodyDef)
        body.createFixture(fixtureDef)
        polygonShape.dispose()
        player.add(BodyComponent(body))
        body.userData = player

        player.add(SpeedComponent(0f))

        val textureAtlas = assetManager.get("textures/textures.atlas", TextureAtlas::class.java)
        val playerTexture = textureAtlas.findRegion("triangle")
        player.add(RenderableComponent { delta, batch ->
            batch.draw(
                    playerTexture,
                    (body.position.x - 0.5f) * 32,
                    (body.position.y - 0.5f) * 32,
                    16f,
                    16f,
                    playerTexture.originalWidth.toFloat(),
                    playerTexture.originalHeight.toFloat(),
                    1f,
                    1f,
                    Math.toDegrees(body.angle.toDouble()).toFloat()
            )
        })

        player.add(DataComponent(0))

        engine.addEntity(player)
        return player
    }

    fun createRay(player: Entity): Entity {
        val ray = Entity()
        val x = BODY.get(player).body.position.x * 32f
        val y = BODY.get(player).body.position.y * 32f
        val angle = BODY.get(player).body.angle
        val rayComponent = RayComponent(x, y, angle)
        ray.add(rayComponent)
        val textureAtlas = assetManager.get("textures/textures.atlas", TextureAtlas::class.java)
        val texture = textureAtlas.findRegion("ray")
        ray.add(RenderableComponent { delta, batch ->
            batch.draw(
                    texture,
                    RAY.get(ray).x,
                    RAY.get(ray).y,
                    0f,
                    0f,
                    1470f,
                    1f,
                    1470f,
                    1f,
                    Math.toDegrees(RAY.get(ray).angle.toDouble()).toFloat()
            )
        })
        engine.addEntity(ray)
        Timer.schedule(object: Timer.Task() {
            override fun run() {
                MainScreen.engine.removeEntity(ray)
            }
        }, 0.5f)
        engine.getEntitiesFor(PLAYER_FAMILY).forEach { entity ->
            if (entity != player) {
                val ex = BODY.get(entity).body.position.x * 32
                val ey = BODY.get(entity).body.position.y * 32
                val ax = RAY.get(ray).x
                val ay = RAY.get(ray).y
                val bx = RAY.get(ray).x + (1500 * Math.cos(RAY.get(ray).angle.toDouble()).toFloat())
                val by = RAY.get(ray).y + (1500 * Math.sin(RAY.get(ray).angle.toDouble()).toFloat())
                val rn = (ex - ax) * (bx - ax) + (ey - ay) * (by - ay)
                val rd = (bx - ax) * (bx - ax) + (by - ay) * (by - ay)
                val r = rn / rd
                val s = ((ay - ey) * (bx - ax) - (ax - ex) * (by - ay)) / rd
                val distanceLine = Math.abs(s) * Math.sqrt(rd.toDouble()).toFloat()
                val distanceSegment = if (r in 0.0..1.0) {
                    distanceLine
                } else {
                    val d1 = (ex - ax) * (ex - ax) + (ey - ay) * (ey - ay)
                    val d2 = (ex - bx) * (ex - bx) + (ey - by) * (ey - by)
                    if (d1 < d2) {
                        Math.sqrt(d1.toDouble()).toFloat()
                    } else {
                        Math.sqrt(d2.toDouble()).toFloat()
                    }
                }
                if (distanceSegment <= 16) {
                    DATA.get(entity).data = 1
                    DATA.get(player).data = 0
                    SOCKET.get(entity).socket.session?.remote?.sendBytesByFuture(ByteBuffer.wrap(byteArrayOf(0x01)))
                    SOCKET.get(entity).socket.session?.remote?.sendBytesByFuture(ByteBuffer.wrap(byteArrayOf(0x02)))
                    SOCKET.get(player).socket.session?.remote?.sendBytesByFuture(ByteBuffer.wrap(byteArrayOf(0x01)))
                    SOCKET.get(player).socket.session?.remote?.sendBytesByFuture(ByteBuffer.wrap(byteArrayOf(0x03)))
                    assetManager.get("sounds/Transfer.wav", Sound::class.java).play()
                }
            }
        }
        return ray
    }

    private fun isLoaded(): Boolean {
        return assetManager.isLoaded("textures/textures.atlas")
            && assetManager.isLoaded("maps/one.tmx")
            && assetManager.isLoaded("sounds/Download.wav")
            && assetManager.isLoaded("sounds/Transfer.wav")
            && assetManager.isLoaded("sounds/Upload.wav")
            && assetManager.isLoaded("fonts/envy_code_r_32.fnt")
            && assetManager.isLoaded("music/Transference Void Music 1 min.ogg")
    }

    private fun onLoad() {
        val renderSystem = engine.getSystem(RenderSystem::class.java)
        val physicsSystem = engine.getSystem(PhysicsSystem::class.java)
        assetManager.get("music/Transference Void Music 1 min.ogg", Music::class.java).play()

        physicsSystem.world.setContactListener(object: ContactListener {
            override fun endContact(contact: Contact) {

            }

            override fun beginContact(contact: Contact) {
                val e1 = contact.fixtureA.body.userData as Entity
                val e2 = contact.fixtureB.body.userData as Entity
                if (DATA_FAMILY.matches(e1) && DATA_FAMILY.matches(e2)) {
                    if (GOAL_FAMILY.matches(e1) || GOAL_FAMILY.matches(e2)) {
                        val goal = if (GOAL_FAMILY.matches(e1)) e1 else e2
                        val player = if (GOAL_FAMILY.matches(e1)) e2 else e1
                        if (DATA.get(player).data > 0) {
                            DATA.get(goal).data++
                            DATA.get(player).data--
                            SOCKET.get(player).socket.session?.remote?.sendBytesByFuture(ByteBuffer.wrap(byteArrayOf(0x03)))
                            assetManager.get("sounds/Upload.wav", Sound::class.java).play()
                        }
                    } else {
                        if (PLAYER_FAMILY.matches(e1)) {
                            DATA.get(e2).data = 0
                            DATA.get(e1).data = 1
                            SOCKET.get(e1).socket.session?.remote?.sendBytesByFuture(ByteBuffer.wrap(byteArrayOf(0x01)))
                            SOCKET.get(e1).socket.session?.remote?.sendBytesByFuture(ByteBuffer.wrap(byteArrayOf(0x02)))
                            assetManager.get("sounds/Download.wav", Sound::class.java).play()
                        } else if (PLAYER_FAMILY.matches(e2)) {
                            DATA.get(e1).data = 0
                            DATA.get(e2).data = 1
                            SOCKET.get(e2).socket.session?.remote?.sendBytesByFuture(ByteBuffer.wrap(byteArrayOf(0x01)))
                            SOCKET.get(e2).socket.session?.remote?.sendBytesByFuture(ByteBuffer.wrap(byteArrayOf(0x02)))
                            assetManager.get("sounds/Download.wav", Sound::class.java).play()
                        }
                    }
                }
            }

            override fun preSolve(contact: Contact, oldManifold: Manifold) {

            }

            override fun postSolve(contact: Contact, impulse: ContactImpulse) {

            }

        })

        val timer = Entity()
        timer.add(TimerComponent(60f))
        val textureAtlas = assetManager.get("textures/textures.atlas", TextureAtlas::class.java)
        val timerTexture = textureAtlas.findRegion("timer")
        val font = assetManager.get("fonts/envy_code_r_32.fnt", BitmapFont::class.java)
        val glyphLayout = GlyphLayout(font, "")
        timer.add(RenderableComponent { delta, batch ->
            batch.draw(timerTexture, (renderSystem.viewport.screenWidth.toFloat() - timerTexture.originalWidth) / 2f, renderSystem.viewport.screenHeight.toFloat() - timerTexture.originalHeight)
            var timerValue = Math.ceil(TIMER.get(timer).timeRemaining.toDouble()).toInt()
            when {
                timerValue == 2 -> timerValue = 3
                timerValue == 1 -> timerValue = 2
                timerValue == 0 -> timerValue = 2
                timerValue == -1 -> timerValue = 1
                timerValue == -2 -> timerValue = 1
                timerValue < -2 -> timerValue = 0
            }
            if (timerValue <= 10) {
                font.color = Color.YELLOW
            }
            if (timerValue <= 3) {
                font.color = Color.RED
            }
            val timerText = Math.max(timerValue, 0).toString()
            glyphLayout.setText(font, timerText)
            font.draw(
                    batch,
                    timerText,
                    (renderSystem.viewport.screenWidth.toFloat() - glyphLayout.width) / 2f,
                    renderSystem.viewport.screenHeight.toFloat() - glyphLayout.height
            )
        })
        engine.addEntity(timer)

//        val box2DDebugRenderer = Box2DDebugRenderer()
//        val b2ddre = Entity()
//        b2ddre.add(RenderableComponent { delta, batch ->
//            batch.end()
//            box2DDebugRenderer.render(physicsSystem.world, renderSystem.camera.combined.scale(32f, 32f, 1f))
//            batch.begin()
//        })
//        engine.addEntity(b2ddre)

        init = true
    }

    fun loadMap() {
        val physicsSystem = engine.getSystem(PhysicsSystem::class.java)
        val renderSystem = engine.getSystem(RenderSystem::class.java)

        val map = assetManager.get("maps/one.tmx", TiledMap::class.java)

        map.layers.get("objects").objects.forEach { obj ->
            val x = obj.properties["x", Float::class.java] / 32f
            val y = obj.properties["y", Float::class.java] / 32f
            val w = obj.properties["width", Float::class.java] / 32f
            val h = obj.properties["height", Float::class.java] / 32f
            when (obj.properties["type", String::class.java]) {
                "wall" -> {
                    val wall = Entity()
                    val bodyDef = BodyDef()
                    bodyDef.type = BodyDef.BodyType.StaticBody
                    bodyDef.position.set(
                            x + (w / 2f),
                            y + (h / 2f)
                    )
                    val shape = PolygonShape()
                    shape.setAsBox(w / 2f, h / 2f)
                    val fixtureDef = FixtureDef()
                    fixtureDef.shape = shape
                    fixtureDef.density = 1f
                    fixtureDef.restitution = 0f
                    fixtureDef.friction = 0f
                    val body = physicsSystem.world.createBody(bodyDef)
                    body.createFixture(fixtureDef)
                    shape.dispose()
                    wall.add(BodyComponent(body))
                    body.userData = wall
                    engine.addEntity(wall)
                }
                "spawn" -> {
                    val spawn = Entity()
                    spawn.add(SpawnComponent(x + (w / 2f), y + (h / 2f)))
                    engine.addEntity(spawn)
                }
                "terminal" -> {
                    val terminal = Entity()
                    val bodyDef = BodyDef()
                    bodyDef.type = BodyDef.BodyType.StaticBody
                    bodyDef.position.set(
                            x + (w / 2f),
                            y + (h / 2f)
                    )
                    val shape = PolygonShape()
                    shape.setAsBox(w / 2f, h / 2f)
                    val fixtureDef = FixtureDef()
                    fixtureDef.shape = shape
                    fixtureDef.density = 1f
                    fixtureDef.restitution = 0f
                    fixtureDef.friction = 0f
                    val body = physicsSystem.world.createBody(bodyDef)
                    body.createFixture(fixtureDef)
                    shape.dispose()
                    terminal.add(BodyComponent(body))
                    terminal.add(DataComponent(0))
                    val random = Random()
                    terminal.add(TimerComponent(random.nextInt(3).toFloat()))
                    val textureAtlas = assetManager.get("textures/textures.atlas", TextureAtlas::class.java)
                    val terminalDataTexture = textureAtlas.findRegion("term_data")
                    terminal.add(RenderableComponent { delta, batch ->
                        if (DATA.get(terminal).data > 0) {
                            batch.draw(
                                    terminalDataTexture,
                                    (BODY.get(terminal).body.position.x - 0.5f) * 32f,
                                    (BODY.get(terminal).body.position.y - 0.5f) * 32f
                            )
                        }
                    })
                    body.userData = terminal
                    engine.addEntity(terminal)
                }
                "goal" -> {
                    val goal = Entity()
                    val bodyDef = BodyDef()
                    bodyDef.type = BodyDef.BodyType.StaticBody
                    bodyDef.position.set(
                            x + (w / 2f),
                            y + (h / 2f)
                    )
                    val shape = PolygonShape()
                    shape.setAsBox(w / 2f, h / 2f)
                    val fixtureDef = FixtureDef()
                    fixtureDef.shape = shape
                    fixtureDef.density = 1f
                    fixtureDef.restitution = 0f
                    fixtureDef.friction = 0f
                    val body = physicsSystem.world.createBody(bodyDef)
                    body.createFixture(fixtureDef)
                    shape.dispose()
                    goal.add(BodyComponent(body))
                    body.userData = goal
                    goal.add(DataComponent(0))
                    goal.add(GoalComponent(5))
                    val textureAtlas = assetManager.get("textures/textures.atlas", TextureAtlas::class.java)
                    val goalTexture = textureAtlas.findRegion("goal_fin")
                    val font = assetManager.get("fonts/envy_code_r_32.fnt", BitmapFont::class.java)
                    val glyphLayout = GlyphLayout(font, "")
                    goal.add(RenderableComponent { delta, batch ->
                        if (DATA.get(goal).data >= GOAL.get(goal).target) {
                            batch.draw(
                                    goalTexture,
                                    (BODY.get(goal).body.position.x - 2f) * 32f,
                                    (BODY.get(goal).body.position.y - 2f) * 32f
                            )
                        } else {
                            val remaining = GOAL.get(goal).target - DATA.get(goal).data
                            glyphLayout.setText(font, remaining.toString())
                            font.color = Color.WHITE
                            font.draw(
                                    batch,
                                    remaining.toString(),
                                    (BODY.get(goal).body.position.x * 32f) - (glyphLayout.width / 2),
                                    (BODY.get(goal).body.position.y * 32f) + (glyphLayout.height / 2)
                            )
                        }
                    })
                    engine.addEntity(goal)
                }
            }
        }

        val mapRenderer = OrthogonalTiledMapRenderer(map, 1f, renderSystem.stage.batch)

        val mapEntity = Entity()

        mapEntity.add(RenderableComponent { delta, batch ->
            batch.end()
            mapRenderer.setView(renderSystem.camera)
            mapRenderer.render()
            batch.begin()
        })
        engine.addEntity(mapEntity)
    }

    override fun render(delta: Float) {
        when {
            init -> engine.update(delta)
            isLoaded() -> onLoad()
            else -> {
                assetManager.update()
            }
        }
    }

    override fun resize(width: Int, height: Int) {
        val renderSystem = engine.getSystem(RenderSystem::class.java)
        renderSystem.viewport.update(width, height)
    }

}