import React, { useEffect, useState } from 'react';
import { map, drawBackground } from '../function/canvasDefault';
import { Col, Row } from 'antd';
import { CONFIG, COLORS } from '../types/configTypes';


import { useCanvasHooks } from '../hooks/useCanvasHooks'; // variables and functions

import useCanvasMouseText from '../hooksUseEffect/useCanvasMouseText';
import useCanvasRetry from '../hooksUseEffect/useCanvasRetry';
import useCanvasUpdatePitch from '../hooksUseEffect/useCanvasUpdatePitch';
import useCanvasAdjustHeight from '../hooksUseEffect/useCanvasAdjustHeight';
import useCanvasInitializeGetPitch from '../hooksUseEffect/useCanvasInitializeGetPitch';
import useCanvasChangeHzAndNotes from '../hooksUseEffect/useCanvasChangeHzAndNotes';
import useCanvasRedrawBackground from '../hooksUseEffect/useCanvasRedrawBackground';

const divisorLocal = 4;

function parseTimeToSeconds(timeString: string): number {
  // Parse the time string in the format "HH:MM:SS:FF" to seconds
  const parts = timeString.split(":");
  const hours = parseInt(parts[0], 10);
  const minutes = parseInt(parts[1], 10);
  const seconds = parseInt(parts[2], 10);
  const frames = parseInt(parts[3], 10);
  const frameDurationInSeconds = frames / 100;

  return hours * 3600 + minutes * 60 + seconds + frameDurationInSeconds;
}

function generatePitchArrayFromJson(jsonData: any[]): number[] {
  // const pitchArray = [];
  const pitchArray: number[] = [];
  const newdArray = [];
  const data = jsonData;
  let previousTimeInSeconds = 0;
  let previousPitch = 0;

  for (let i = 0; i < data.length; i++) {
    const currentTimeInSeconds = parseTimeToSeconds(data[i].time);
    const timeDifference = currentTimeInSeconds - previousTimeInSeconds;

    // Fill the array with the previous pitch value for the duration of timeDifference
    for (let j = 0; j < timeDifference * 100; j++) {
      pitchArray.push(previousPitch);
    }

    previousTimeInSeconds = currentTimeInSeconds;
    previousPitch = data[i].pitch;
  }

  // Ensure the array ends at the last time point
  const endTimeInSeconds = parseTimeToSeconds(data[data.length - 1].time);
  const endTimeDifference = endTimeInSeconds - previousTimeInSeconds;
  for (let j = 0; j < endTimeDifference * 100; j++) {
    pitchArray.push(previousPitch);
  }
  const extendedArray: number[] = [];
  pitchArray.forEach((pitch) => {
    extendedArray.push(pitch);
    for (let i = 0; i < divisorLocal; i++) {
      extendedArray.push(pitch);
    }
  });

  while (extendedArray.length > 1400) {
    extendedArray.pop();
  }
  while (extendedArray.length < 1400) {
    extendedArray.push(0);
  }

  return extendedArray;

  // return pitchArray;
}

interface FixedProps {
  size: number[];
  config: CONFIG;
  isPlaying: boolean;
  COLORS: COLORS;
  showNotesPar: boolean;
  playLyricCount: number;
  gender: string;
  syllableCount: string;
  genderName: string;
  onPlayLyricCountChange: (newCount: number) => void;
  setBaseFilenames: (filenames: string[]) => void;
  onAudioSrcChange: (newAudioSrc: string) => void;
  isRetry: boolean;
  setPlayingPause: () => void;
  divisor: number;
  initialRange: number[];
  isListen: number;
  setIsListen: (num: number) => void;
  setMaxLyricCount: (num: number) => void;
}

const Fixed: React.FC<FixedProps> = ({
  setMaxLyricCount,
  setIsListen,
  isListen,
  initialRange,
  divisor,
  isRetry,
  setPlayingPause,
  gender,
  genderName,
  onAudioSrcChange,
  setBaseFilenames,
  syllableCount,
  onPlayLyricCountChange,
  config,
  COLORS,
  size,
  playLyricCount,
  isPlaying,
  showNotesPar }) => {
  const {
    pitch,
    setPitch,
    mouseHeight,
    realVoiceColor,
    targetVoiceColor,
    closeVoiceColor,
    ballYCurr,
    canvasRef,
    rectWidth,
    CanvasLength,
    canvasHeight,
    setCanvasHeight,
    showNotes,
    setShowNotes,
    offset,
    currentX,
    setCurrentX,
    initialcurrentX,
    notesLabel,
    setNotesLabel,
    freqLabel,
    setFreqLabel,

    updateBallY,
    updateCanvasHeight,
    handleMouseMove,
  } = useCanvasHooks(size, divisor, COLORS, initialRange);
  const [shouldDisabled, setShouldDisabled] = useState<boolean>(false);
  const desiredLength = Math.floor(size[1] / divisor);
  const initialColorChangesFull = new Array(CanvasLength).fill(false);
  const initialBallHistoryFull = new Array(CanvasLength).fill(NaN);
  const [colorChangesFull, setColorChangesFull] = useState<boolean[]>(initialColorChangesFull);
  const [ballHistoryFull, setBallHistoryFull] = useState<number[]>(initialBallHistoryFull);
  const resetStatesFull = () => {
    setColorChangesFull([...initialColorChangesFull]);
    setBallHistoryFull([...initialBallHistoryFull]);
    setCurrentX(initialcurrentX);
  };
  const [pitchArray, setPitchArray] = useState<number[]>([]);
  const [jsonFiles, setJsonFiles] = useState<string[]>([]);
  const [audioSrc, setAudioSrc] = useState<string>('');

  // +++++++++++++++++++++++++++++++++++++++++++++++++
  // Hooks useEffect parts \\

  // initialize get pitch function + pause + draw background
  useCanvasInitializeGetPitch(config, setPitch, updateCanvasHeight, setPlayingPause, canvasRef, initialRange, showNotes);

  // set canvas size for different resolutions
  useCanvasAdjustHeight(canvasRef, setCanvasHeight);

  // store current pitch
  useCanvasUpdatePitch(isPlaying, pitch, updateBallY);

  // update mouse text
  useCanvasMouseText(canvasRef, mouseHeight, initialRange);

  // change Notes and hz display
  useCanvasChangeHzAndNotes(setShowNotes, showNotes, showNotesPar, canvasRef, initialRange, setNotesLabel, setFreqLabel, canvasHeight);

  // retry
  useCanvasRetry(isRetry, divisor, setPlayingPause, resetStatesFull);

  // redraw background when hz or notes changed
  useCanvasRedrawBackground(canvasRef, showNotesPar, initialRange);


  // +++++++++++++++++++++++++++++++++++++++++++++++++
  // Unique useEffect parts \\

  // render user curve
  useEffect(() => {

    const canvas = canvasRef.current;
    if (canvas) {
      const ctx = canvas.getContext('2d');
      if (ctx) {
        // ctx.clearRect(0, 0, canvas.width, canvas.height);
        const textX = canvas.width - 100;
        const textY = 20;
        ctx.clearRect(0, 0, textX - 5, canvas.height);
        ctx.clearRect(0, textY + 5, canvas.width, canvas.height - (textY + 5));

        drawBackground(canvasRef, initialRange[1], initialRange[0], showNotes);

        for (let i = 0 + offset; i < pitchArray.length; i++) {
          const mappedJsonValue = map(pitchArray[i], initialRange[0], initialRange[1], size[0], 0);
          // Set fill color based on condition
          if (colorChangesFull[i]) {
            ctx.fillStyle = closeVoiceColor;
          } else {
            ctx.fillStyle = targetVoiceColor;
          }

          // Draw a rectangle
          ctx.fillRect(i, mappedJsonValue, rectWidth, rectWidth);

        }


        for (var i = 0; i < ballHistoryFull.length; i++) {
          ctx.beginPath();
          const mappedValue = map(ballHistoryFull[i], initialRange[0], initialRange[1], size[0], -1);

          ctx.arc(i, mappedValue, 5, 0, 2 * Math.PI);
          ctx.fillStyle = realVoiceColor;
          ctx.fill();
          ctx.closePath();
        }

        // Plot current value
        ctx.beginPath();
        ctx.arc(currentX * divisor, ballYCurr, 10, 0, 2 * Math.PI);
        ctx.fillStyle = "black";
        ctx.fill();
        ctx.closePath();
      }
    }

  }, [COLORS, initialRange, pitchArray, ballHistoryFull, showNotes, showNotesPar]);

  // render listen curve
  useEffect(() => {
    resetStatesFull();
    setPlayingPause();
    const canvas = canvasRef.current;
    let animationFrameId: number;

    if (isListen > 1 && canvas) {
      const ctx = canvas.getContext('2d');
      if (ctx) {
        // ctx.clearRect(0, 0, canvas.width, canvas.height);
        const textX = canvas.width - 100;
        const textY = 20;
        ctx.clearRect(0, 0, textX - 5, canvas.height);
        ctx.clearRect(0, textY + 5, canvas.width, canvas.height - (textY + 5));

        drawBackground(canvasRef, initialRange[1], initialRange[0], showNotes);

        let drawnUntil = 0;
        let lastFrameTime = Date.now();

        const draw = () => {
          const now = Date.now();
          const deltaTime = now - lastFrameTime;
          const elementsPerFrame = deltaTime / (1000 / 6) * 135;
          for (let j = 0; j < elementsPerFrame && drawnUntil < pitchArray.length; j++, drawnUntil++) {
            const i = drawnUntil;
            const mappedJsonValue = map(pitchArray[drawnUntil], initialRange[0], initialRange[1], size[0], 0);
            ctx.fillStyle = closeVoiceColor;
            ctx.fillRect(i, mappedJsonValue, rectWidth + 3, rectWidth + 3);
          }

          if (drawnUntil < pitchArray.length) {
            lastFrameTime = now;
            animationFrameId = requestAnimationFrame(draw);
          } else {
            cancelAnimationFrame(animationFrameId);
          }
        };

        draw();

        return () => {
          if (animationFrameId) {
            cancelAnimationFrame(animationFrameId);
          }
        };
      }
    }
  }, [isListen, pitchArray]);

  useEffect(() => {
    setIsListen(1);
  }, [pitchArray]);

  // next
  useEffect(() => {
    setPlayingPause();
    resetStatesFull();
  }, [playLyricCount])

  useEffect(() => {
    let url = '';
    if (gender === 'male') {
      url = 'https://ceas5.uc.edu/transvoice/jsondata/' + syllableCount + '/' + gender + '/list.json';
    }
    else {
      url = 'https://ceas5.uc.edu/transvoice/jsonDataOm/' + gender + '/' + genderName + '/' + syllableCount + 'syllable/list.json';
    }

    if (jsonFiles.length > playLyricCount) {
      // Get the correct filename based on playLyricCount
      const filename = jsonFiles[playLyricCount];

      // fetch(`http://127.0.0.1:8000/data/${syllableCount}/${gender}/${filename}`)

      if (gender === 'male') {
        url = 'https://ceas5.uc.edu/transvoice/jsondata/' + syllableCount + '/' + gender + '/' + filename;
      }
      else {
        url = 'https://ceas5.uc.edu/transvoice/jsonDataOm/' + gender + '/' + genderName + '/' + syllableCount + 'syllable/' + filename;
      }
      fetch(url)
        .then(response => {
          if (!response.ok) {
            throw new Error('Network response was not ok');
          }
          return response.json();
        })
        .then(data => {
          const pitches = generatePitchArrayFromJson(data.data);
          setPitchArray(pitches);
        })
        .catch(error => {
          console.error('Error fetching the JSON file:', error);
        });
    }
  }, [playLyricCount, jsonFiles]);

  useEffect(() => {
    let url = '';
    if (gender === 'male') {
      url = 'https://ceas5.uc.edu/transvoice/jsondata/' + syllableCount + '/' + gender + '/list.json';
    }
    else {
      url = 'https://ceas5.uc.edu/transvoice/jsonDataOm/' + gender + '/' + genderName + '/' + syllableCount + 'syllable/list.json';
    }
    // const url = `https://ceas5.uc.edu/transvoice/jsonDataOm/${gender}/${genderName}/${syllableCount}syllable/list.json`;

    fetch(url)
      .then(response => {
        if (!response.ok) {
          throw new Error('Network response was not ok');
        }
        return response.json();
      })
      .then(data => {
        setJsonFiles(data.json_files);

        const modifiedArray = data.base_filenames.map((element: any) => element.replaceAll('_', ' ')); // replace all occurrences of underscore
        setMaxLyricCount(data.base_filenames.length - 1);
        setBaseFilenames(modifiedArray);

        // Check if there is a file name
        if (data.base_filenames.length > 0) {
          // Get the contents of the first file
          let url = '';
          if (gender === 'male') {
            url = 'https://ceas5.uc.edu/transvoice/jsondata/' + syllableCount + '/' + gender + '/' + data.json_files[0];
          }
          else {
            url = 'https://ceas5.uc.edu/transvoice/jsonDataOm/' + gender + '/' + genderName + '/' + syllableCount + 'syllable/' + data.json_files[0];
          }

          return fetch(url);
        } else {
          throw new Error('No files available');
        }
      })
      .then(response => {
        if (!response.ok) {
          throw new Error('Network response was not ok');
        }
        return response.json();
      })
      .then(firstFileData => {
        const pitches = generatePitchArrayFromJson(firstFileData.data);
        setPitchArray(pitches);
      })
      .catch(error => {
        console.error('Error:', error);
      });

    onPlayLyricCountChange(0);

  }, [gender, genderName, syllableCount]);

  useEffect(() => {
    if (jsonFiles.length > 0) {
      // Construct the URL of the audio file
      // const audioFilename = `${gender}-${jsonFiles[0]}.wav`;
      let audioFilename = '';
      let audioUrl = '';
      if (gender === 'male') {
        audioFilename = `${gender}-${jsonFiles[playLyricCount].replace('.json', '')}.wav`;
        audioUrl = `https://ceas5.uc.edu/transvoice/audio/${syllableCount}/${gender}/${audioFilename}`;
      }
      else {
        audioFilename = `${genderName}-${gender}-${jsonFiles[playLyricCount].replace('.json', '')}.wav`;
        audioUrl = `https://ceas5.uc.edu/transvoice/audioOm/${gender}/${genderName}/${syllableCount}syllable/${audioFilename}`;
      }
      setAudioSrc(audioUrl);
    }
  }, [syllableCount, gender, playLyricCount, jsonFiles]);

  useEffect(() => {
    onAudioSrcChange(audioSrc);
  }, [audioSrc, onAudioSrcChange]);

  const updateBallHistoryFull = (pitch: number) => {
    let tempHistoryFull = [...ballHistoryFull];
    let ctxdiv = currentX * divisor;

    let ballYtem = map(pitch, initialRange[0], initialRange[1], size[0], -1)
    if (currentX < desiredLength - 1) {
      setShouldDisabled(false);
      tempHistoryFull[ctxdiv] = pitch;
      setColorChangesFull(currentColors => {
        const newColors = [...currentColors];
        const mappedJsonValue = map(pitchArray[ctxdiv], initialRange[0], initialRange[1], size[0], 0);
        const difference = Math.abs(mappedJsonValue - ballYtem);
        if (difference <= 50 && !isNaN(difference)) {
          for (let j = ctxdiv - divisor; j <= ctxdiv + divisor && j < CanvasLength; j++) {
            newColors[j] = true;
          }
        }
        return newColors;
      });

    } else {
      setShouldDisabled(true);
      if (!shouldDisabled) {
        setPlayingPause();
      }
    }
    setCurrentX(currentX + 1);
    setBallHistoryFull(tempHistoryFull);
  };

  useEffect(() => {
    if (isPlaying) {
      if (pitch !== null) {
        updateBallHistoryFull(pitch);
      } else {
        updateBallHistoryFull(0);
      }
    }
  }, [pitch, isPlaying]);

  useEffect(() => {
    if (shouldDisabled && isPlaying) {
      resetStatesFull();
      setShouldDisabled(false);
    }
  }, [isPlaying]);


  return (
    <Row style={{ maxHeight: "27vw", marginBottom: "5vw", maxWidth: "100vw" }}>
      <Col span={1}>
      </Col>
      <Col span={22}>
        <div className="yAxisArea" style={{ height: canvasHeight }}>
          <Row style={{ height: canvasHeight }}>
            <Col span={8}>
              {showNotesPar
                ? <div style={{ height: canvasHeight }} className="yAxisLabel">Pitch (Notes)</div>
                : <div style={{ height: canvasHeight }} className="yAxisLabel">Pitch (Hz)</div>}
            </Col>
            <Col span={8}>
              <div className="yAxisNumbers" style={{ height: canvasHeight * 1.039 }}>
                {showNotesPar
                  ? notesLabel?.map((note, index) => <div key={index}>{note}</div>) ?? []
                  : freqLabel?.map((freq, index) => <div key={index}>{freq}</div>) ?? []}
              </div>
            </Col>
            <Col span={8}>
              <div className="yAxisLines" style={{ height: canvasHeight }}>
                {freqLabel?.map((_, index) => (
                  <div></div>
                )) ?? []}
              </div>
            </Col>
          </Row>
        </div>

        <div style={{ height: canvasHeight }}>
          <canvas
            ref={canvasRef}
            onMouseMove={handleMouseMove}
            id="pitchCanvas"
            width={size[1]}
            height={size[0]}
            style={{ border: '1px solid #000' }}
          ></canvas>
          <div
            className='XAxisNum'
            style={{
              top: canvasHeight,
              width: canvasHeight * 3.486,

            }}
          >
            {[...Array(11)].map((_, index) => (
              <div key={index} style={{ position: 'relative' }}>
                <div className="timeMarkerLine" />
                <span className='timeMarkerNum'>
                  {((15 / (divisor / 4)) * (index / 10)).toFixed(1)}
                </span>
              </div>
            ))}
          </div>
          <div
            style={{
              top: canvasHeight * (1.12),
              width: canvasHeight * 3.486,
            }}
            className='XAxis'
          >
            <div>Time (Seconds)</div>

          </div>

        </div>

      </Col>
      <Col span={1}>
      </Col>
    </Row>
  );
}

export default Fixed;