package space.nstart.pcp.pcp_request_service.utils

import ballistics.types.OPKatObj
import clipper.IntPoint
import clipper.Path
import clipper.containsPoint
import org.locationtech.jts.io.WKTReader
import space.nstart.pcp.pcp_request_service.configuration.CustomValidationException
import space.nstart.pcp.pcp_types_lib.dto.ballistics.ObjDTO
import space.nstart.pcp.pcp_types_lib.dto.ballistics.PositionDTO
import kotlin.math.PI
import kotlin.math.abs
import kotlin.math.sqrt


class ContourClipService {



    fun prceWKT(wkt : String) : Iterable<PositionDTO>?{
        try {
            val contour = mutableListOf<PositionDTO>()
            val reader = WKTReader()
            val geom = reader.read(wkt)
            for (c in geom.coordinates)
                contour.add(PositionDTO(c.y, c.x, c.z))
            if (contour.size < 3)
                return null
            return contour
        } catch (ex : Exception){
            throw CustomValidationException("Ошибка пазбора WKT-формата")
        }
    }

    fun getObjContour(obj : ObjDTO) : Iterable<PositionDTO>?{
        obj.contourWKT?.let { return prceWKT(obj.contourWKT!!) }
        return null
    }


    fun splitPolySides(poly : Iterable<PositionDTO>, internal : Boolean = true) : Iterable<OPKatObj>{

//        return  poly.map { p1 ->
//            OPKatObj(
//                1, 1, "", 1, p1.lat, p1.long,
//                0.0, 0.0, PI / 2, -PI / 2
//            )
//        }
        val res = mutableListOf<OPKatObj>()
        for (i in 1 ..poly.count()-1){
            res.addAll(splitLine(poly.elementAt(i-1), poly.elementAt(i)))
        }
//        if (internal)
        res.addAll(splitPoly(poly, 2.0 * PI / 180.0))
        return res
    }

    private fun splitLine(p1 : PositionDTO, p2 : PositionDTO) : Iterable<OPKatObj>{
        var x1 = p1.long
        var x2 = p2.long
        if (abs(x1 - x2) > PI){
            if (x1 < x2)
                x1 += 2 * PI
            else
                x2 = 2 * PI
        }
        if (x1 > x2){
            val buf = x1
            x1 = x2
            x2 = buf
        }
        var y1 = p1.lat
        var y2 = p2.lat
        if (y1 > y2){
            val buf = y1
            y1 = y2
            y2 = buf
        }
        val dx = x2 - x1
        val dy = y2 - y1
        val dl = sqrt((x2-x1)*(x2-x1)+(y2-y1)*(y2-y1))
        val res = mutableListOf<OPKatObj>()
        res.add(OPKatObj(1, 1, "", 1, p1.lat, p1.long, 0.0, 0.0, PI / 2, -PI / 2))
        if (dl > 0.01) {
            res.add(OPKatObj(1, 1, "", 2, p2.lat, p2.long, 0.0, 0.0, PI / 2, -PI / 2))
            var pn = 3
            val step = 0.1 * PI / 180
            if (dx > dy) {
                var x = x1 + step
                val k = dy / dx
                val b = y2 - k * x2
                (0..(dx / step).toInt()).forEach { i ->
                    if (x < x2) {
                        val y = k * x + b
                        res.add(OPKatObj(1, 1, "", pn, y, x, 0.0, 0.0, PI / 2, -PI / 2))
                        ++pn
                    }
                    x += step
                }
            } else {
                if (dx < 0.000001) {
                    var y = y1 + step
                    (0..(dy / step).toInt()).forEach { i ->
                        if (y < y2) {
                            res.add(OPKatObj(1, 1, "", pn, y, x1, 0.0, 0.0, PI / 2, -PI / 2))
                            ++pn
                        }
                        y += step
                    }
                } else {
                    var y = y1 + step
                    val k = dy / dx
                    val b = y2 - k * x2
                    (0..(dy / step).toInt()).forEach { i ->
                        if (y < y2) {
                            val x = (y - b) / k
                            res.add(OPKatObj(1, 1, "", pn, y, x, 0.0, 0.0, PI / 2, -PI / 2))
                            ++pn
                        }
                        y += step
                    }
                }
            }
        }
        return res
    }

    private fun splitPoly(poly : Iterable<PositionDTO>, step : Double? = null) : Iterable<OPKatObj> {
        val res = mutableListOf<OPKatObj>()
        val path = Path()
        val clipVal = 1000000
        var bMin = poly.minOf { it.lat }
        val bMax = poly.maxOf { it.lat }
        var lMin = poly.minOf { it.long }
        var lMax = poly.maxOf { it.long }
        for (p in poly) {
            path.add(IntPoint((p.lat * clipVal).toLong(), (p.long * clipVal).toLong()))
            if (step != null)
                res.add(OPKatObj(1, 1, "", 1, p.lat, p.long, 0.0, 0.0, PI / 2, -PI / 2))
        }
        path.add(path.first())
        if ((lMin < 0 && lMax > 0) && (lMax - lMin > PI)) {
            val b = lMax
            lMax = lMin + PI * 2
            lMin = b
        }
        if (lMax - lMin > PI) {
            val b = lMax
            lMax = lMin
            lMin = b - 2 * PI
        }
        var stepL : Double
        var stepB : Double
        if  (step != null){
            stepL = step
            stepB = step
        }else {
            stepL = (lMax - lMin) / 10
            if (stepL > 0.1 * PI / 180)
                stepL = 0.1 * PI / 180
            stepB = (bMax - bMin) / 10
            if (stepB > 0.1 * PI / 180)
                stepB = 0.1 * PI / 180
        }
        while (bMin <= bMax) {
            var l = lMin
            while (l <= lMax) {
                if (path.containsPoint(IntPoint( (bMin * clipVal).toLong(), (l * clipVal).toLong())) == 1)
                    res.add(OPKatObj(1, 1, "", 1, bMin, l, 0.0, 0.0, PI / 2, -PI / 2))
                l += stepL
            }
            bMin += stepB
        }
        return res
    }
}