/*
 * Copyright (C) 2023-2024  Yomitan Authors
 * Copyright (C) 2021-2022  Yomichan Authors
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

import {getKanaDiacriticInfo, isMoraPitchHigh} from '../../language/ja/japanese.js';

/**
 * @param {string[]} morae
 * @param {number} downstepPosition
 * @param {number[]} nasalPositions
 * @param {number[]} devoicePositions
 * @returns {HTMLSpanElement}
 */
export function createPronunciationText(morae, downstepPosition, nasalPositions, devoicePositions) {
    const nasalPositionsSet = nasalPositions.length > 0 ? new Set(nasalPositions) : null;
    const devoicePositionsSet = devoicePositions.length > 0 ? new Set(devoicePositions) : null;
    const container = document.createElement('span');
    container.className = 'pronunciation-text';
    for (let i = 0, ii = morae.length; i < ii; ++i) {
        const i1 = i + 1;
        const mora = morae[i];
        const highPitch = isMoraPitchHigh(i, downstepPosition);
        const highPitchNext = isMoraPitchHigh(i1, downstepPosition);
        const nasal = nasalPositionsSet !== null && nasalPositionsSet.has(i1);
        const devoice = devoicePositionsSet !== null && devoicePositionsSet.has(i1);

        const n1 = document.createElement('span');
        n1.className = 'pronunciation-mora';
        n1.dataset.position = `${i}`;
        n1.dataset.pitch = highPitch ? 'high' : 'low';
        n1.dataset.pitchNext = highPitchNext ? 'high' : 'low';

        const characterNodes = [];
        for (const character of mora) {
            const n2 = document.createElement('span');
            n2.className = 'pronunciation-character';
            n2.textContent = character;
            n1.appendChild(n2);
            characterNodes.push(n2);
        }

        if (devoice) {
            n1.dataset.devoice = 'true';
            const n3 = document.createElement('span');
            n3.className = 'pronunciation-devoice-indicator';
            n1.appendChild(n3);
        }
        if (nasal && characterNodes.length > 0) {
            n1.dataset.nasal = 'true';

            const group = document.createElement('span');
            group.className = 'pronunciation-character-group';

            const n2 = characterNodes[0];
            const character = /** @type {string} */ (n2.textContent);

            const characterInfo = getKanaDiacriticInfo(character);
            if (characterInfo !== null) {
                n1.dataset.originalText = mora;
                n2.dataset.originalText = character;
                n2.textContent = characterInfo.character;
            }

            let n3 = document.createElement('span');
            n3.className = 'pronunciation-nasal-diacritic';
            n3.textContent = '\u309a'; // Combining handakuten
            group.appendChild(n3);

            n3 = document.createElement('span');
            n3.className = 'pronunciation-nasal-indicator';
            group.appendChild(n3);

            /** @type {ParentNode} */ (n2.parentNode).replaceChild(group, n2);
            group.insertBefore(n2, group.firstChild);
        }

        const line = document.createElement('span');
        line.className = 'pronunciation-mora-line';
        n1.appendChild(line);

        container.appendChild(n1);
    }
    return container;
}

/**
 * @param {string[]} morae
 * @param {number} downstepPosition
 * @returns {SVGSVGElement}
 */
export function createPronunciationGraph(morae, downstepPosition) {
    const ii = morae.length;

    const svgns = 'http://www.w3.org/2000/svg';
    const svg = document.createElementNS(svgns, 'svg');
    svg.setAttribute('xmlns', svgns);
    svg.setAttribute('class', 'pronunciation-graph');
    svg.setAttribute('focusable', 'false');
    svg.setAttribute('viewBox', `0 0 ${50 * (ii + 1)} 100`);

    if (ii <= 0) { return svg; }

    const path1 = document.createElementNS(svgns, 'path');
    svg.appendChild(path1);

    const path2 = document.createElementNS(svgns, 'path');
    svg.appendChild(path2);

    const pathPoints = [];
    for (let i = 0; i < ii; ++i) {
        const highPitch = isMoraPitchHigh(i, downstepPosition);
        const highPitchNext = isMoraPitchHigh(i + 1, downstepPosition);
        const x = i * 50 + 25;
        const y = highPitch ? 25 : 75;
        if (highPitch && !highPitchNext) {
            addGraphDotDownstep(svg, svgns, x, y);
        } else {
            addGraphDot(svg, svgns, x, y);
        }
        pathPoints.push(`${x} ${y}`);
    }

    path1.setAttribute('class', 'pronunciation-graph-line');
    path1.setAttribute('d', `M${pathPoints.join(' L')}`);

    pathPoints.splice(0, ii - 1);
    {
        const highPitch = isMoraPitchHigh(ii, downstepPosition);
        const x = ii * 50 + 25;
        const y = highPitch ? 25 : 75;
        addGraphTriangle(svg, svgns, x, y);
        pathPoints.push(`${x} ${y}`);
    }

    path2.setAttribute('class', 'pronunciation-graph-line-tail');
    path2.setAttribute('d', `M${pathPoints.join(' L')}`);

    return svg;
}

/**
 * @param {number} downstepPosition
 * @returns {HTMLSpanElement}
 */
export function createPronunciationDownstepPosition(downstepPosition) {
    const downstepPositionString = `${downstepPosition}`;

    const n1 = document.createElement('span');
    n1.className = 'pronunciation-downstep-notation';
    n1.dataset.downstepPosition = downstepPositionString;

    let n2 = document.createElement('span');
    n2.className = 'pronunciation-downstep-notation-prefix';
    n2.textContent = '[';
    n1.appendChild(n2);

    n2 = document.createElement('span');
    n2.className = 'pronunciation-downstep-notation-number';
    n2.textContent = downstepPositionString;
    n1.appendChild(n2);

    n2 = document.createElement('span');
    n2.className = 'pronunciation-downstep-notation-suffix';
    n2.textContent = ']';
    n1.appendChild(n2);

    return n1;
}

// Private

/**
 * @param {Element} container
 * @param {string} svgns
 * @param {number} x
 * @param {number} y
 */
function addGraphDot(container, svgns, x, y) {
    container.appendChild(createGraphCircle(svgns, 'pronunciation-graph-dot', x, y, '15'));
}

/**
 * @param {Element} container
 * @param {string} svgns
 * @param {number} x
 * @param {number} y
 */
function addGraphDotDownstep(container, svgns, x, y) {
    container.appendChild(createGraphCircle(svgns, 'pronunciation-graph-dot-downstep1', x, y, '15'));
    container.appendChild(createGraphCircle(svgns, 'pronunciation-graph-dot-downstep2', x, y, '5'));
}

/**
 * @param {Element} container
 * @param {string} svgns
 * @param {number} x
 * @param {number} y
 */
function addGraphTriangle(container, svgns, x, y) {
    const node = document.createElementNS(svgns, 'path');
    node.setAttribute('class', 'pronunciation-graph-triangle');
    node.setAttribute('d', 'M0 13 L15 -13 L-15 -13 Z');
    node.setAttribute('transform', `translate(${x},${y})`);
    container.appendChild(node);
}

/**
 * @param {string} svgns
 * @param {string} className
 * @param {number} x
 * @param {number} y
 * @param {string} radius
 * @returns {Element}
 */
function createGraphCircle(svgns, className, x, y, radius) {
    const node = document.createElementNS(svgns, 'circle');
    node.setAttribute('class', className);
    node.setAttribute('cx', `${x}`);
    node.setAttribute('cy', `${y}`);
    node.setAttribute('r', radius);
    return node;
}