/** @jsx jsx */
import { jsx, keyframes, css } from '@emotion/core';
import styled from '@emotion/styled';
import Timeline from './Timeline/Timeline';
import { navHeight } from '../styles/dimensions';
import { useCallback, useContext, useEffect, useRef } from 'react';
import { useProject } from '../hooks/useProject';
import audioContext from '../audio/utils/audioContext';
import useKeyCommand, { Key as K, Modifier as M } from '../hooks/useKeyCommand';
import {ReactComponent as BackToStart} from '../icons/BackToStart.svg';
import {ReactComponent as Play} from '../icons/Play.svg';
import {ReactComponent as Stop} from '../icons/Stop.svg';
import {ReactComponent as Record} from '../icons/Record.svg';
import {ReactComponent as ExportIcon} from '../icons/Export.svg';
import { ProjectContext } from '../hooks/useProject';
import useLibraryKeyCommand from '../hooks/useLibraryKeyCommand';
import { getSelectedTracks } from '../audio/utils/selectionTools';
import { useTimelineViewController, TimelineViewControllerContext } from '../hooks/useTimelineViewController';
import { useScreenConfiguration, ScreenConfigurationContext } from '../hooks/useScreenConfiguration';
import CommandRunner from './CommandRunner/CommandRunner';
import { gray200, gray700, blue500, red500, gray800 } from '../styles/colors_v2';
import { ClearButton, MinorButton } from '../styles/elements';
import Toolbar from './Toolbar/Toolbar';
import downloadAsWav from '../audio/utils/downloadAsWav';
import sumAudio from '../audio/audioFunctions/sumAudio';
import { CommandEditorContext, useCommandEditor } from '../hooks/useCommandEditor';
import fadeIn from '../audio/commands/simpleAudioCommands/fadeIn';
import deleteEdit from '../audio/commands/simpleAudioCommands/deleteEdit';
import nudgeLeft from '../audio/commands/projectCommands/nudgeLeft';
import nudgeRight from '../audio/commands/projectCommands/nudgeRight';
import fadeOut from '../audio/commands/simpleAudioCommands/fadeOut';
import { AuthContext } from '../hooks/useAuth';
import { AuthState } from '../types';
import LoginButton from './LoginButton';
import concatBuffers from '../audio/audioFunctions/concatBuffers';
import useProjectPlayer, { PlayerContext } from '../hooks/useProjectPlayer';
import useStateAutoLoader from '../hooks/useStateAutoLoader';
import BrowserWarning from '../BrowserWarning';
import Welcome from './Welcome/Welcome';
import { useProjectManagement, ProjectManagementContext } from '../hooks/useProjectManagement';
import { useWalkthrough, WalkthroughContext, WalkthroughStep } from '../hooks/useWalkthrough';
import WalkthroughFlag from './WalkthroughFlag';
import useAnimationFrame from '../hooks/useAnimationFrame';
import { CommandLibraryContext, useCommandLibrary } from '../hooks/useCommandLibrary';

const AllWrapper = styled.div`
  width: 100vw;
  height: 100vh;
  overflow: hidden;
  flex-grow: 1;
  position: relative;
  overflow: hidden;
  display: grid;
  grid-template-columns: auto 1fr;
`;

const blink = keyframes`
  0% { opacity: 50%; }
  40% { opacity: 50%; }
  50% { opacity: 100%; }
  90% { opacity: 100%; }
  100% { opacity: 50%; }
`;

const PlaybackButton = styled(ClearButton)`
  svg * {
    stroke: ${gray700};
  }
  &:hover {
    svg * {
      stroke: currentColor;
    }
  }
`;

const PlayButton = styled(PlaybackButton)`
  color: transparent;
  &:hover {
    color: ${blue500};
  }
`;

const StopButton = styled(PlaybackButton)`
  color: transparent;
  &:hover {
    color: ${blue500};
  }
`;

const RecordButton = styled(PlaybackButton)<{isRecording: boolean}>`
  color: transparent;
  &:hover {
    color: ${red500};
  }
  ${({ isRecording }) => isRecording
    ? css`
      color: ${red500};
      svg { animation: ${blink} 0.8s linear infinite; }
      `
    : ''
  }
`;

const ControlsWrapper = styled.div`
  button {
    height: 100%;
    padding: 0 10px;
  }
`;

const AccountDropdownWrapper = styled.div`
  padding-right: 14px;
  > svg {
    height: 15px;
    width: auto;
    * {
      fill: ${gray700};
    }
  }
`;

const TimelineNavigatorWrapper = styled.div`
  height: 100%;
`;

const NavWrapper = styled.header`
  width: 100%;
  background-color: ${gray200};
  display: flex;
  justify-content: space-between;
  align-items: center;
  position: relative;
  z-index: 3;
  > * {
    display: flex;
    align-items: center;
    height: 100%;
  }
`;

const UserProfile = styled.div`
  display: flex;
  align-items: center;
  color: ${gray700};
  padding-right: 8px;
  img {
    width: 24px;
    height: 24px;
    border-radius: 4px;
  }
  span {
    display: block;
    margin-left: 10px;
    font-size: 13px;
  }
`;

const OtherInteractions = styled.div`
  display: flex;
  align-items: stretch;
  padding: 6px;
  height: 100%;
  margin-left: 20px;
`;

const OtherInteractionButton = styled(MinorButton)`
  color: ${gray700};
  border-color: currentColor;
  height: 100%;
  font-size: 13px;
  margin: 0 4px;
  &:hover {
    color: ${blue500};
  }
`;

const ExportButton = styled(OtherInteractionButton)`
  > svg {
    margin-right: 5px;
  }
  > svg * {
    fill: currentColor;
    stroke: transparent;
  }
`;

const PlayWalkthroughWrapper = styled.div`
  display: contents;
  svg {
    position: relative;
    bottom: -2px;
  }
  svg * {
    stroke: ${gray800};
  }
`;

let hasOpenLinkedCommand = false;

export default () => {
  const project = useProject(audioContext.sampleRate);
  const player = useProjectPlayer(project.state);
  const screenConfiguration = useScreenConfiguration();
  const timelineViewController = useTimelineViewController(player.duration, screenConfiguration.timelineViewportWidth);

  const { interactions, recorder } = project;
  const { play, seek, pause, paused, duration, fastGetCurrentTimeRef } = player;
  const { startRecording, stopRecording, isRecording } = recorder;

  const hasAutoLoaded = useStateAutoLoader(!recorder.isRecording, project.state, project.loadProjectState);

  const handleStartRecording = useCallback(
    () => {
      startRecording(() => {
        interactions.timelineContent.insertNewRecordingTrack(paused ? 0 : fastGetCurrentTimeRef.current());
        interactions.selection.set([[0, 0], []]);
        if (paused) {
          play(0);
        }
      });
    },
    [interactions.timelineContent, interactions.selection, startRecording, play, paused, fastGetCurrentTimeRef]
  );

  const handleStop = useCallback(
    () => {
      stopRecording();
      pause();
    },
    [stopRecording, pause]
  );

  const lastState = useRef(project.state);
  useEffect(() => {
    if (!paused && !isRecording && lastState.current !== project.state) {
      handleStop();
    }
    lastState.current = project.state
  }, [paused, isRecording, project.state, handleStop]);

  const wasRecording = useRef(isRecording);
  useEffect(() => {
    if (wasRecording.current && !isRecording) {
      interactions.timelineContent.normalizeTrackContents();
    }
    wasRecording.current = isRecording;
  }, [isRecording, interactions.timelineContent])

  const backToStart = useCallback(() => {
    handleStop();
    seek(0, true);
    timelineViewController.interactions.setScrollLeft(0);
  }, [seek, timelineViewController, handleStop]);

  const playbackStopper = useCallback(
    () => {
      if (!paused && !isRecording && fastGetCurrentTimeRef.current() > duration) {
        handleStop();
      }
    },
    [paused, isRecording, fastGetCurrentTimeRef, duration, handleStop]
  );

  useAnimationFrame(playbackStopper, !paused && !isRecording);

  useLibraryKeyCommand(interactions.timelineContent.executeCommand, [M.Ctrl, K.Comma], fadeIn);
  useLibraryKeyCommand(interactions.timelineContent.executeCommand, [M.Ctrl, K.Period], fadeOut);
  useLibraryKeyCommand(interactions.timelineContent.executeCommand, [K.Backspace], deleteEdit);
  useLibraryKeyCommand(interactions.timelineContent.executeCommand, [M.Ctrl, K.Left], nudgeLeft);
  useLibraryKeyCommand(interactions.timelineContent.executeCommand, [M.Ctrl, K.Right], nudgeRight);

  useKeyCommand([M.Ctrl, K.X], interactions.timelineContent.cut);
  useKeyCommand([M.Ctrl, K.C], interactions.timelineContent.copy);
  useKeyCommand([M.Ctrl, K.V], interactions.timelineContent.paste);
  useKeyCommand([M.Ctrl, K.Z], interactions.undo);
  useKeyCommand([M.Ctrl, M.Shift, K.Z], interactions.redo);
  useKeyCommand([M.Ctrl, K.Y], interactions.redo);
  useKeyCommand(
    [M.Ctrl, K.A],
    useCallback(
      () => {
        interactions.selection.set([
          [0, player.duration],
          getSelectedTracks(project.selection)
        ])
      },
      [interactions.selection, project.selection, player.duration]
    )
  );

  const walkthrough = useWalkthrough();

  useKeyCommand(
    K.Space,
    useCallback(
      () => {
        if (paused) {
          walkthrough.play();
          play();
        } else {
          handleStop();
        }
      },
      [walkthrough, play, handleStop, paused]
    )
  );

  const handleDragEnter = useCallback(
    (e) => {
      e.preventDefault();
      e.stopPropagation();
    },
    []
  );

  const handleDragLeave = useCallback(
    (e) => {
      e.preventDefault();
      e.stopPropagation();
    },
    []
  );

  const handleDragOver = useCallback(
    (e) => {
      e.preventDefault();
      e.stopPropagation();
    },
    []
  );

  const handleDrop = useCallback(
    (e) => {
      Promise.all(Array.from(e.dataTransfer.files).map((f: any) => f.arrayBuffer().then(audioContext.decodeAudioData.bind(audioContext))))
        .then((buffers) => interactions.timelineContent.loadAudioBufferLists(buffers.map((b) => [b])))
        .catch((e: Error) => {
          console.error(e);
        });
      e.preventDefault();
      e.stopPropagation();
    },
    [interactions.timelineContent]
  );

  const quickExport = useCallback(
    () => {
      const outputAudioBuffer = sumAudio(
        project.state.tracks.map(({ audioBuffers }) => concatBuffers(audioBuffers)),
        true,
        [0, 0]
      );
      downloadAsWav(outputAudioBuffer);
    },
    [project]
  );

  const auth = useContext(AuthContext);
  const projectManagement = useProjectManagement(project.state, project.loadProjectState);

  const isEmpty = project.state.tracks.length === 0;

  useEffect(() => {
    if (!isEmpty) {
      walkthrough.seeAudio();
    }
  }, [isEmpty, walkthrough]);

  const commandEditor = useCommandEditor();
  const commandLibrary = useCommandLibrary();

  useEffect(() => {
    if (!hasOpenLinkedCommand && commandLibrary.linkedCommand) {
      hasOpenLinkedCommand = true;
      commandEditor.openSavedCommand(commandLibrary.linkedCommand);
      screenConfiguration.setCommandViewHeight(50);
    }
  }, [commandLibrary, commandEditor, screenConfiguration]);

  return (
    <WalkthroughContext.Provider value={walkthrough}>
      <ProjectContext.Provider value={project}>
        <ScreenConfigurationContext.Provider value={screenConfiguration}>
          <CommandLibraryContext.Provider value={commandLibrary}>
            <CommandEditorContext.Provider value={commandEditor}>
              <PlayerContext.Provider value={player}>
                <ProjectManagementContext.Provider value={projectManagement}>
                  <AllWrapper
                    onDrop={handleDrop}
                    onDragOver={handleDragOver}
                    onDragEnter={handleDragEnter}
                    onDragLeave={handleDragLeave}
                    style={{
                      gridTemplateRows: `
                        ${navHeight}px
                        ${screenConfiguration.commandViewHeight < 5 ? 100 : 100 - screenConfiguration.commandViewHeight}fr
                        ${screenConfiguration.commandViewHeight < 5 ? '27px' : `${screenConfiguration.commandViewHeight}fr`}
                      `
                    }}
                  >
                    <Toolbar />
                    <NavWrapper>
                      <ControlsWrapper>
                        <StopButton onClick={backToStart}><BackToStart /></StopButton>
                        <StopButton onClick={handleStop}><Stop /></StopButton>
                        <PlayButton onClick={() => { walkthrough.play(); play(); }}><Play /></PlayButton>
                        <WalkthroughFlag
                          step={WalkthroughStep.Play}
                          style={{ top: 'calc(100% - 8px)', pointerEvents: 'none' }}
                          anchorStyle={{ left: 87, top: 5 }}
                        >
                          <PlayWalkthroughWrapper>
                            <strong><BackToStart /> Rewind</strong><br />
                            <strong><Stop /> Stop</strong><br />
                            <strong><Play /> Play</strong><br />
                            <strong><Record /> Record</strong><br /><br />
                            You can also press the <strong>spacebar</strong> to play &amp; stop!
                          </PlayWalkthroughWrapper>
                        </WalkthroughFlag>
                        <RecordButton onClick={isRecording ? handleStop : handleStartRecording} isRecording={isRecording}><Record /></RecordButton>

                        <BrowserWarning />


                        <OtherInteractions onClick={quickExport}>
                          <ExportButton>
                            <ExportIcon /> Export Wav
                          </ExportButton>
                        </OtherInteractions>

                        <OtherInteractions>
                          <OtherInteractionButton onClick={interactions.undo}>Undo</OtherInteractionButton>
                          <OtherInteractionButton onClick={interactions.redo}>Redo</OtherInteractionButton>
                        </OtherInteractions>

                        <OtherInteractions>
                          <OtherInteractionButton onClick={interactions.timelineContent.cut}>Cut</OtherInteractionButton>
                          <OtherInteractionButton onClick={interactions.timelineContent.copy}>Copy</OtherInteractionButton>
                          <OtherInteractionButton onClick={interactions.timelineContent.paste}>Paste</OtherInteractionButton>
                        </OtherInteractions>

                        <OtherInteractions>
                          <OtherInteractionButton onClick={timelineViewController.interactions.zoomIn}>Zoom +</OtherInteractionButton>
                          <OtherInteractionButton onClick={timelineViewController.interactions.zoomOut}>Zoom -</OtherInteractionButton>
                          <OtherInteractionButton onClick={timelineViewController.interactions.showAll}>Show All</OtherInteractionButton>
                        </OtherInteractions>
                      </ControlsWrapper>
                      <TimelineNavigatorWrapper>

                      </TimelineNavigatorWrapper>
                      <AccountDropdownWrapper>
                        {auth.state === AuthState.LoggedOut && <LoginButton />}
                        {auth.state === AuthState.LoggedIn && <UserProfile><img alt={auth.user.username} src={auth.user.avatar} /><span>{auth.user.username}</span></UserProfile>}
                      </AccountDropdownWrapper>
                    </NavWrapper>
                    {
                      isEmpty && hasAutoLoaded ? (
                        <Welcome
                          startRecording={handleStartRecording}
                        />
                      ) : (
                        <TimelineViewControllerContext.Provider value={timelineViewController}>
                          <Timeline />
                        </TimelineViewControllerContext.Provider>
                      )
                    }
                    <CommandRunner visible={screenConfiguration.codeWindowOpen} />
                  </AllWrapper>
                </ProjectManagementContext.Provider>
              </PlayerContext.Provider>
            </CommandEditorContext.Provider>
          </CommandLibraryContext.Provider>
        </ScreenConfigurationContext.Provider>
      </ProjectContext.Provider>
    </WalkthroughContext.Provider>
  );
}
