<script setup lang="ts">
import { Head } from '@inertiajs/vue3';
import { useEcho } from '@laravel/echo-vue';
import {
    Chart as ChartJS,
    Title,
    Tooltip,
    Legend,
    BarElement,
    CategoryScale,
    LinearScale,
    PointElement,
    LineElement,
    ChartDataset,
    ChartTypeRegistry,
    BubbleDataPoint,
    Point,
    ChartData,
} from 'chart.js';
import { onMounted, ref } from 'vue';
import { Bar, Chart, Line } from 'vue-chartjs';
import LinkHeader from '@/components/LinkHeader.vue';

ChartJS.register(
    Title,
    Tooltip,
    Legend,
    BarElement,
    CategoryScale,
    LinearScale,
    PointElement,
    LineElement,
);
ChartJS.defaults.font.size = 16;
ChartJS.defaults.color = '#FFF';
ChartJS.defaults.backgroundColor = '#AAA';

const props = defineProps<{
    type: string;
    dataset: number[][];
    sessionId: string;
}>();

console.log('Session ID:', props.sessionId);

useEcho(
    `${props.sessionId}-perceptron-training`,
    'PerceptronTrainingIteration',
    percpetronIteration,
    [{}],
    'public',
);
useEcho(
    `${props.sessionId}-perceptron-training`,
    'PerceptronTrainingEnded',
    perceptronTrainingEnded,
    [{}],
    'public',
);

onMounted(() => {
    // make a POST request to start the perceptron training
    fetch('/api/perceptron/run', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
        },
        credentials: 'same-origin',
        body: JSON.stringify({
            type: 'simple',
            min_error: 0.01,
            session_id: props.sessionId,
        }),
    })
        .then((response) => {
            if (!response.ok) {
                throw new Error('Network response was not ok');
            }
            return response.json();
        })
        .then((data) => {
            console.log('Perceptron training started:', data);
        })
        .catch((error) => {
            console.error('Error starting perceptron training:', error);
        });
});

const iterations = ref<
    {
        iteration: number;
        exampleIndex: number;
        weights: number[];
        error: number;
    }[]
>([]);

function percpetronIteration(data: any) {
    console.log('Received perceptron iteration data:', data);
    iterations.value.push({
        iteration: data.iteration,
        exampleIndex: data.exampleIndex,
        weights: data.synaptic_weights,
        error: data.error,
    });
}

const trainingEnded = ref(false);
const trainingEndReason = ref('');
function perceptronTrainingEnded(data: any) {
    console.log('Perceptron training ended:', data);
    trainingEnded.value = true;
    trainingEndReason.value = data.reason;
}

// Separate data into each dataset based on value of the last column (label)
const cleanedDataset: { label: number; data: { x: number; y: number }[] }[] =
    [];
props.dataset.forEach((row) => {
    const label = row[row.length - 1];
    const dataPoint = { x: row[0], y: row[1] };

    let dataset = cleanedDataset.find((d) => d.label === label);
    if (!dataset) {
        dataset = { label, data: [] };
        cleanedDataset.push(dataset);
    }
    dataset.data.push(dataPoint);
});

function getPerceptronDecisionBoundaryDataset(
    weights: number[],
): ChartDataset<
    keyof ChartTypeRegistry,
    number | Point | [number, number] | BubbleDataPoint | null
> {
    const label = 'Ligne de décision du Perceptron';

    if (weights.length == 3) {
        // Simple line
        return {
            type: 'line',
            label: label,
            data: [
                {
                    x: -1,
                    y:
                        -(weights[1] / weights[2]) * -1 -
                        weights[0] / weights[2],
                },
                {
                    x: 2,
                    y: -(weights[1] / weights[2]) * 2 - weights[0] / weights[2],
                },
            ],
            borderColor: '#FFF',
            borderWidth: 2,
            pointRadius: 0,
        };
    } else {
        return {
            type: 'scatter',
            label: label,
            data: (() => {
                const decisionBoundary = [];
                const latestWeights =
                    iterations.value.length > 0
                        ? iterations.value[iterations.value.length - 1].weights
                        : [0, 0, 0]; // default weights if no iterations yet
                for (let x = -2; x <= 2; x += 0.05) {
                    for (let y = -2; y <= 2; y += 0.05) {
                        let value = 0;
                        for (let i = 0; i < latestWeights.length - 1; i++) {
                            value += latestWeights[i] * (i === 0 ? x : y); // TODO : Fix formula
                        }
                        value += latestWeights[0]; // bias

                        if (Math.abs(value) < 0.003) {
                            decisionBoundary.push({ x: x, y: y });
                        }
                    }
                }
                return decisionBoundary;
            })(),
            backgroundColor: '#FFF',
        };
    }
}

/**
 * Return the datasets of the iterations with the form { label: `Exemple ${exampleIndex}`, data: [error for iteration 1, error for iteration 2, ...] }
 */
function getPerceptronErrorsPerIteration(): ChartData<
    'bar',
    (number | [number, number] | null)[]
>[] {
    const datasets: ChartData<'bar', (number | [number, number] | null)[]>[] =
        [];

    const backgroundColors = [
        '#FF6384',
        '#36A2EB',
        '#FFCE56',
        '#4BC0C0',
        '#9D5C5C',
        '#8B4513',
        '#2E8B57',
        '#800080',
    ];

    iterations.value.forEach((iteration) => {
        const exampleLabel = `Exemple ${iteration.exampleIndex}`;
        let dataset = datasets.find((d) => d.label === exampleLabel);
        if (!dataset) {
            dataset = {
                label: exampleLabel,
                data: [],
                backgroundColor:
                    backgroundColors[
                        iteration.exampleIndex % backgroundColors.length
                    ],
            };
            datasets.push(dataset);
        }
        dataset.data.push(iteration.error);
    });

    // Sort dataset by label (Exemple 0, Exemple 1, ...)
    datasets.sort((a, b) => {
        const aIndex = parseInt(a.label.split(' ')[1]);
        const bIndex = parseInt(b.label.split(' ')[1]);
        return aIndex - bIndex;
    });

    return datasets;
}
</script>

<template>
    <Head title="Perceptron Viewer"></Head>
    <main class="space-y-6">
        <LinkHeader class="w-full" />
        <div
            class="align-items-start justify-content-center flex h-full min-h-dvh max-w-dvw"
        >
            <div class="max-h-full w-full overflow-y-scroll">
                <table
                    class="table w-full border-collapse border border-gray-300"
                >
                    <tr class="text-left" v-if="iterations.length > 0">
                        <th>Itération</th>
                        <th>Exemple</th>
                        <th
                            v-for="(weight, index) in iterations[0].weights"
                            v-bind:key="index"
                        >
                            X<sub>{{ index }}</sub>
                        </th>
                        <th>Erreur</th>
                    </tr>
                    <tr
                        v-for="(iteration, index) in iterations"
                        v-bind:key="index"
                        :class="{
                            'bg-gray-900': iteration.iteration % 2 === 0,
                        }"
                    >
                        <td>{{ iteration.iteration }}</td>
                        <td>{{ iteration.exampleIndex }}</td>
                        <td
                            v-for="(weight, index) in iteration.weights"
                            v-bind:key="index"
                        >
                            {{ weight.toFixed(2) }}
                        </td>
                        <td>{{ iteration.error.toFixed(2) }}</td>
                    </tr>

                    <tr v-if="trainingEnded" class="bg-red-900 text-center">
                        <td colspan="100%">
                            <strong>Entraînement terminé :</strong>
                            {{ trainingEndReason }}
                        </td>
                    </tr>
                </table>
            </div>
            <div class="h-full w-full">
                <div>
                    <Chart
                        class="flex"
                        :options="{
                            responsive: true,
                            maintainAspectRatio: true,
                            plugins: {
                                legend: {
                                    position: 'top',
                                },
                                title: {
                                    display: true,
                                    text: 'Ligne de décision du Perceptron',
                                },
                            },
                            layout: {
                                padding: {
                                    left: 10,
                                    right: 10,
                                    top: 10,
                                    bottom: 10,
                                },
                            },
                            scales: {
                                x: {
                                    type: 'linear',
                                    position: 'bottom',
                                },
                                y: {
                                    type: 'linear',
                                    position: 'left',
                                },
                            },
                        }"
                        :data="{
                            datasets: [
                                // Points from the dataset
                                ...cleanedDataset.map((dataset, index) => ({
                                    type: 'scatter',
                                    label: `Classe ${dataset.label}`,
                                    data: dataset.data,
                                    backgroundColor:
                                        [
                                            '#FF6384',
                                            '#36A2EB',
                                            '#FFCE56',
                                            '#4BC0C0',
                                            '#9D5C5C',
                                            '#8B4513',
                                            '#2E8B57',
                                            '#800080',
                                        ][index] || '#AAA',
                                })),

                                // Perceptron decision boundary
                                getPerceptronDecisionBoundaryDataset(
                                    iterations.length > 0
                                        ? iterations[iterations.length - 1]
                                              .weights
                                        : [0, 0, 0],
                                ),
                            ],
                        }"
                    />
                </div>
                <div>
                    <Bar
                        class="flex"
                        :options="{
                            responsive: true,
                            maintainAspectRatio: true,
                            plugins: {
                                title: {
                                    display: true,
                                    text: 'Nombre d\'erreurs par itération',
                                },
                            },
                            scales: {
                                x: {
                                    stacked: true,
                                    min: 0,
                                },
                                y: {
                                    stacked: true,
                                    beginAtZero: true,
                                },
                            },
                        }"
                        :data="{
                            labels: iterations.reduce((labels, iteration) => {
                                if (
                                    !labels.includes(
                                        `Itération ${iteration.iteration}`,
                                    )
                                ) {
                                    labels.push(
                                        `Itération ${iteration.iteration}`,
                                    );
                                }
                                return labels;
                            }, [] as string[]),
                            datasets: getPerceptronErrorsPerIteration(),
                        }"
                    />
                </div>
            </div>
        </div>
    </main>
</template>
