import React, { useEffect, useState } from 'react';

type Props = {
    setFunc: (newPoints: { p1: number[]; p2: number[] }) => void;
    currPoints: { p1: number[]; p2: number[] };
    easeFunc: string;
};

const ANIMATION_DURATION = 750;

const CONTAINER_WIDTH = 200;
const EDITOR_WIDTH = 100;
const OFFSET = (CONTAINER_WIDTH - EDITOR_WIDTH) / 2;

export const TIMING_DEFAULT_MAPPINGS = {
    ease: 'cubic-bezier(0.25, 0.1, 0.25, 1.0)',
    linear: 'cubic-bezier(0.0, 0.0, 1.0, 1.0)',
    'ease-in': 'cubic-bezier(0.42, 0, 1.0, 1.0)',
    'ease-out': 'cubic-bezier(0, 0, 0.58, 1.0)',
    'ease-in-out': 'cubic-bezier(0.42, 0, 0.58, 1.0)',
};

/**
 * Visual editor for a bezier curve with draggable control points.
 * */

const CubicBezierEditor = ({ setFunc, currPoints, easeFunc }: Props) => {
    const [animating, setAnimating] = useState(false);
    const [c1Down, setC1Down] = useState(false);
    const [c2Down, setC2Down] = useState(false);

    const roundToHundredth = (num: number) => {
        return Math.round(num * 100) / 100;
    };

    const convertToPoints = (func: string) => {
        let strPoints = func ? func : 'ease';
        if (!strPoints.startsWith('cubic')) {
            switch (func) {
                case 'linear':
                    strPoints = 'cubic-bezier(0.0, 0.0, 1.0, 1.0)';
                    break;
                case 'ease':
                    strPoints = 'cubic-bezier(0.25, 0.1, 0.25, 1.0)';
                    break;
                case 'ease-in':
                    strPoints = 'cubic-bezier(0.42, 0, 1.0, 1.0)';
                    break;
                case 'ease-out':
                    strPoints = 'cubic-bezier(0, 0, 0.58, 1.0)';
                    break;
                case 'ease-in-out':
                    strPoints = 'cubic-bezier(0.42, 0, 0.58, 1.0)';
                    break;
                default:
                    strPoints = 'cubic-bezier(0.25, 0.1, 0.25, 1.0)';
            }
        }
        const components = strPoints
            .split('(')[1]
            .split(')')[0]
            .split(',')
            .map(elem => parseFloat(elem));
        return {
            p1: [components[0], components[1]],
            p2: [components[2], components[3]],
        };
    };

    useEffect(() => {
        if (animating) {
            setTimeout(() => {
                setAnimating(false);
            }, ANIMATION_DURATION * 2);
        }
    }, [animating]);

    useEffect(() => {
        if (!c1Down) return;
        window.addEventListener('pointerup', () => {
            setC1Down(false);
        });
        const handlePointerMove = (e: PointerEvent) => {
            const newX = currPoints.p1[0] + e.movementX / EDITOR_WIDTH;
            if (newX < 0 || newX > 1) {
                return;
            }

            setFunc({
                ...currPoints,
                p1: [roundToHundredth(currPoints.p1[0] + e.movementX / EDITOR_WIDTH), roundToHundredth(currPoints.p1[1] - e.movementY / EDITOR_WIDTH)],
            });
        };

        window.addEventListener('pointermove', handlePointerMove);

        return () => window.removeEventListener('pointermove', handlePointerMove);
    }, [c1Down, currPoints]);

    // Sets up pointer events for moving the control points
    useEffect(() => {
        if (!c2Down) return;
        window.addEventListener('pointerup', () => {
            setC2Down(false);
        });
        const handlePointerMove = (e: PointerEvent) => {
            const newX = currPoints.p2[0] + e.movementX / EDITOR_WIDTH;
            if (newX < 0 || newX > 1) {
                return;
            }

            setFunc({
                ...currPoints,
                p2: [roundToHundredth(currPoints.p2[0] + e.movementX / EDITOR_WIDTH), roundToHundredth(currPoints.p2[1] - e.movementY / EDITOR_WIDTH)],
            });
        };

        window.addEventListener('pointermove', handlePointerMove);

        return () => window.removeEventListener('pointermove', handlePointerMove);
    }, [c2Down, currPoints]);

    return (
        <div
            onPointerMove={e => {
                e.stopPropagation;
            }}>
            <svg className="presBox-bezier-editor" width={`${CONTAINER_WIDTH}`} height={`${CONTAINER_WIDTH}`} xmlns="http://www.w3.org/2000/svg">
                {/* Outlines */}
                <line x1={`${0 + OFFSET}`} y1={`${EDITOR_WIDTH + OFFSET}`} x2={`${EDITOR_WIDTH + OFFSET}`} y2={`${0 + OFFSET}`} stroke="#c1c1c1" strokeWidth="1" />
                {/* Box Outline */}
                <rect x={`${0 + OFFSET}`} y={`${0 + OFFSET}`} width={EDITOR_WIDTH} height={EDITOR_WIDTH} stroke="#c5c5c5" fill="transparent" strokeWidth="1" />
                {/* Editor */}
                <path
                    d={`M ${0 + OFFSET} ${EDITOR_WIDTH + OFFSET} C ${currPoints.p1[0] * EDITOR_WIDTH + OFFSET} ${EDITOR_WIDTH - currPoints.p1[1] * EDITOR_WIDTH + OFFSET}, ${
                        currPoints.p2[0] * EDITOR_WIDTH + OFFSET
                    } ${EDITOR_WIDTH - currPoints.p2[1] * EDITOR_WIDTH + OFFSET}, ${EDITOR_WIDTH + OFFSET} ${0 + OFFSET}`}
                    stroke="#ffffff"
                    fill="transparent"
                />
                {/* Bottom left */}
                <line
                    onPointerDown={() => {
                        setC1Down(true);
                    }}
                    onPointerUp={() => {
                        setC1Down(false);
                    }}
                    x1={`${0 + OFFSET}`}
                    y1={`${EDITOR_WIDTH + OFFSET}`}
                    x2={`${currPoints.p1[0] * EDITOR_WIDTH + OFFSET}`}
                    y2={`${EDITOR_WIDTH - currPoints.p1[1] * EDITOR_WIDTH + OFFSET}`}
                    stroke="#00000000"
                    strokeWidth="5"
                />
                <line x1={`${0 + OFFSET}`} y1={`${EDITOR_WIDTH + OFFSET}`} x2={`${currPoints.p1[0] * EDITOR_WIDTH + OFFSET}`} y2={`${EDITOR_WIDTH - currPoints.p1[1] * EDITOR_WIDTH + OFFSET}`} stroke="#ffffff" strokeWidth="1" />
                <circle
                    cx={`${currPoints.p1[0] * EDITOR_WIDTH + OFFSET}`}
                    cy={`${EDITOR_WIDTH - currPoints.p1[1] * EDITOR_WIDTH + OFFSET}`}
                    r="5"
                    fill={`${c1Down ? '#3fa9ff' : '#ffffff'}`}
                    onPointerDown={e => {
                        e.stopPropagation();
                        setC1Down(true);
                    }}
                    onPointerUp={e => {
                        setC1Down(false);
                    }}
                />
                {/* Top right */}
                <line
                    onPointerDown={e => {
                        e.stopPropagation();
                        setC2Down(true);
                    }}
                    onPointerUp={e => {
                        setC2Down(false);
                    }}
                    x1={`${EDITOR_WIDTH + OFFSET}`}
                    y1={`${0 + OFFSET}`}
                    x2={`${currPoints.p2[0] * EDITOR_WIDTH + OFFSET}`}
                    y2={`${EDITOR_WIDTH - currPoints.p2[1] * EDITOR_WIDTH + OFFSET}`}
                    stroke="#00000000"
                    strokeWidth="5"
                />
                <line x1={`${EDITOR_WIDTH + OFFSET}`} y1={`${0 + OFFSET}`} x2={`${currPoints.p2[0] * EDITOR_WIDTH + OFFSET}`} y2={`${EDITOR_WIDTH - currPoints.p2[1] * EDITOR_WIDTH + OFFSET}`} stroke="#ffffff" strokeWidth="1" />
                <circle
                    cx={`${currPoints.p2[0] * EDITOR_WIDTH + OFFSET}`}
                    cy={`${EDITOR_WIDTH - currPoints.p2[1] * EDITOR_WIDTH + OFFSET}`}
                    r="5"
                    fill={`${c2Down ? '#3fa9ff' : '#ffffff'}`}
                    onPointerDown={e => {
                        e.stopPropagation();
                        setC2Down(true);
                    }}
                    onPointerUp={e => {
                        setC2Down(false);
                    }}
                />
            </svg>
        </div>
    );
};

export default CubicBezierEditor;
