<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\DataDBD;
use App\Models\Kecamatan;
use App\Charts\MonthlyDBDChart;
use App\Charts\YearlyDBDChart;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Session;

class KMeansController extends Controller
{
    public function dashboard(YearlyDBDChart $yearlyChart, Request $request)
    {
        // Ambil daftar tahun yang ada di database
        $availableYears = DataDBD::select('tahun')->distinct()->orderBy('tahun')->pluck('tahun');

        // Jika tidak ada tahun yang dipilih, gunakan tahun pertama yang tersedia
        $selectedYear = $request->input('year', $availableYears->first());

        // Ambil data jumlah kasus DBD dari database untuk tahun tertentu
        $dataByYear = DataDBD::where('tahun', $selectedYear)->get();

        // Buat objek MonthlyDBDChart dengan memberikan data dan tahun yang dipilih
        $monthlyChart = new MonthlyDBDChart($dataByYear, $selectedYear);

        // Bangun chart untuk jumlah kasus DBD setiap bulannya
        $monthlyChartData = $monthlyChart->build();

        // // Bangun chart untuk jumlah kasus DBD setiap tahunnya
        $yearlyChartData = $yearlyChart->build();

        $clusterResults = Session::get('clusterResults');

        // Kembalikan view dengan chart yang dibuat dan daftar tahun yang tersedia
        return view('dashboard', [
            'clusterResults' => $clusterResults,
            'monthlyChart' => $monthlyChartData,
            'yearlyChart' => $yearlyChartData,
            'selectedYear' => $selectedYear,
            'availableYears' => $availableYears // Sertakan daftar tahun yang tersedia
        ]);
    }

    public function index()
    {
        // Mengambil data dari tabel DataDBD
        $data = DataDBD::all();
        $kecamatanData = Kecamatan::all()->keyBy('id')->toArray();

        // Menggabungkan jumlah kasus dan jumlah penduduk untuk kecamatan yang sama dari semua tahun
        $dataset = [];
        foreach ($data as $d) {
            $key = $d->id_kecamatan;
            if (!isset($dataset[$key])) {
                $dataset[$key] = [
                    'id_kecamatan' => $d->id_kecamatan,
                    'nama_kecamatan' => $kecamatanData[$d->id_kecamatan]['nama_kecamatan'] ?? 'Unknown',
                    'latitude' => $kecamatanData[$d->id_kecamatan]['latitude'] ?? null,
                    'longitude' => $kecamatanData[$d->id_kecamatan]['longitude'] ?? null,
                    'jumlah_penduduk' => (int) $kecamatanData[$d->id_kecamatan]['jumlah_penduduk'],
                    'jumlah_kasus' => (int) $d->jumlah_kasus,
                    'cases_per_capita' => (int) $d->jumlah_kasus / max((int) $kecamatanData[$d->id_kecamatan]['jumlah_penduduk'], 1) // Prevent division by zero
                ];
            } else {
                $dataset[$key]['jumlah_kasus'] += (int) $d->jumlah_kasus;
                $dataset[$key]['cases_per_capita'] = $dataset[$key]['jumlah_kasus'] / max((int) $dataset[$key]['jumlah_penduduk'], 1); // Prevent division by zero
            }
        }

        // Mengubah array asosiatif menjadi array numerik
        $dataset = array_values($dataset);

        // Menjalankan algoritma K-Means
        $k = 3;
        $result = $this->kMeans($dataset, $k);

        // Menghitung tingkat kasus berdasarkan perhitungan statistik
        $caseLevels = $this->calculateCaseLevels($dataset);

        // Mengelompokkan hasil clustering berdasarkan kecamatan
        $clusterResults = [];
        foreach ($result['clusters'] as $clusterIndex => $cluster) {
            foreach ($cluster as $datum) {
                $clusterResults[$datum['id_kecamatan']] = [
                    'id_kecamatan' => $datum['id_kecamatan'],
                    'nama_kecamatan' => $datum['nama_kecamatan'],
                    'latitude' => $datum['latitude'],
                    'longitude' => $datum['longitude'],
                    'cluster' => $clusterIndex + 1,
                    'cases_per_capita' => $datum['cases_per_capita'],
                    'jumlah_penduduk' => $datum['jumlah_penduduk'],
                    'jumlah_kasus' => $datum['jumlah_kasus'],
                    'tingkat_kasus' => $caseLevels[$datum['id_kecamatan']]
                ];
            }
        }

        Session::put('clusterResults', $clusterResults);

        // Menampilkan hasil perhitungan K-Means
        return view('kmeans.index', [
            'steps' => $result['steps'],
            'clusterResults' => $clusterResults,
            'kecamatanData' => $kecamatanData
        ]);
    }

    private function calculateCaseLevels($dataset)
    {
        $casesPerCapita = array_column($dataset, 'cases_per_capita');
        $mean = array_sum($casesPerCapita) / count($casesPerCapita);
        $stdDev = sqrt(array_sum(array_map(fn($x) => pow($x - $mean, 2), $casesPerCapita)) / count($casesPerCapita));

        $thresholdLow = $mean - $stdDev;
        $thresholdHigh = $mean + $stdDev;

        $caseLevels = [];
        foreach ($dataset as $data) {
            if ($data['cases_per_capita'] <= $thresholdLow) {
                $caseLevels[$data['id_kecamatan']] = 'Rendah';
            } elseif ($data['cases_per_capita'] >= $thresholdHigh) {
                $caseLevels[$data['id_kecamatan']] = 'Tinggi';
            } else {
                $caseLevels[$data['id_kecamatan']] = 'Sedang';
            }
        }

        return $caseLevels;
    }
    

    private function kMeans($data, $k)
    {
        // Inisialisasi centroids secara acak
        $centroids = [];
        $usedKecamatanIds = []; 
        // Array untuk menyimpan ID kecamatan yang sudah digunakan sebagai centroid

        for ($i = 0; $i < $k; $i++) {
            // Pilih kecamatan secara acak
            do {
                $randomIndex = array_rand($data);
                $randomKecamatanId = $data[$randomIndex]['id_kecamatan'];
            } while (in_array($randomKecamatanId, $usedKecamatanIds)); 
            // Periksa apakah kecamatan sudah digunakan sebagai centroid sebelumnya

            // Tambahkan kecamatan ke array centroid dan array kecamatan yang sudah digunakan
            $centroids[$i] = $data[$randomIndex];
            $usedKecamatanIds[] = $randomKecamatanId;
        }

        $iterations = 100;
        $steps = [];
        for ($i = 0; $i < $iterations; $i++) {
            $clusters = array_fill(0, $k, []);

            // Menempatkan setiap data ke klaster terdekat
            foreach ($data as $datum) {
                $distances = [];
                foreach ($centroids as $centroid) {
                    $distances[] = $this->euclideanDistance($datum, $centroid);
                }
                $cluster = array_keys($distances, min($distances))[0];
                $clusters[$cluster][] = $datum;
            }

            // Menyimpan langkah perhitungan
            $steps[] = [
                'iteration' => $i + 1,
                'centroids' => $centroids,
                'clusters' => $clusters
            ];

            // Menghitung centroid baru
            $newCentroids = [];
            foreach ($clusters as $cluster => $clusterData) {
                $newCentroids[$cluster] = $this->calculateCentroid($clusterData);
            }

            // Memeriksa konvergensi
            if ($this->centroidsConverged($centroids, $newCentroids)) {
                break;
            }

            // Memperbarui centroid untuk iterasi berikutnya
            $centroids = $newCentroids;
        }

        return ['clusters' => $clusters, 'steps' => $steps];
    }

    private function euclideanDistance($datum1, $datum2)
    {
        return sqrt(
            pow($datum1['jumlah_penduduk'] - $datum2['jumlah_penduduk'], 2) +
            pow($datum1['jumlah_kasus'] - $datum2['jumlah_kasus'], 2)
        );
    }

    private function calculateCentroid($clusterData)
    {
        $centroid = [
            'id_kecamatan' => null,
            'nama_kecamatan' => '',
            'jumlah_penduduk' => 0,
            'jumlah_kasus' => 0
        ];
        $count = count($clusterData);

        // Pastikan jumlah data tidak nol sebelum melakukan pembagian
        if ($count > 0) {
            foreach ($clusterData as $data) {
                $centroid['jumlah_penduduk'] += (int) $data['jumlah_penduduk'];
                $centroid['jumlah_kasus'] += (int) $data['jumlah_kasus'];
            }

            $centroid['jumlah_penduduk'] /= $count;
            $centroid['jumlah_kasus'] /= $count;

            // Mengatur id_kecamatan dari salah satu data terdekat ke centroid baru
            $closestDatum = $this->getClosestDatum($centroid, $clusterData);
            $centroid['id_kecamatan'] = $closestDatum['id_kecamatan'];
            $centroid['nama_kecamatan'] = $closestDatum['nama_kecamatan'];
        }

        return $centroid;
    }

    private function getClosestDatum($centroid, $clusterData)
    {
        $minDistance = PHP_FLOAT_MAX;
        $closestDatum = null;

        foreach ($clusterData as $datum) {
            $distance = $this->euclideanDistance($centroid, $datum);
            if ($distance < $minDistance) {
                $minDistance = $distance;
                $closestDatum = $datum;
            }
        }

        return $closestDatum;
    }

    private function centroidsConverged($centroids, $newCentroids)
    {
        foreach ($centroids as $key => $centroid) {
            if ($this->euclideanDistance($centroid, $newCentroids[$key]) > 0.0001) {
                return false;
            }
        }
        return true;
    }
}
