/*
 * Copyright (C) 2022 Savoir-faire Linux Inc.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Affero 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public
 * License along with this program.  If not, see
 * <https://www.gnu.org/licenses/>.
 */
import Check from '@mui/icons-material/Check';
import RefreshOutlined from '@mui/icons-material/RefreshOutlined';
import { Avatar, AvatarProps, Box, Dialog, Stack, Typography } from '@mui/material';
import { useCallback, useEffect, useState } from 'react';
import { useDropzone } from 'react-dropzone';
import { useTranslation } from 'react-i18next';

import CallPermissionDenied from '../pages/CallPermissionDenied';
import { FileHandler } from '../utils/files';
import {
  CancelPictureButton,
  CloseButton,
  CornerCloseButton,
  EditPictureButton,
  RecordButton,
  RoundButton,
  TakePictureButton,
  UploadPictureButton,
} from './Button';
import { InfosDialog, useDialogHandler } from './Dialog';
import { CircleMaskOverlay, FileDragOverlay } from './Overlay';

type ConversationAvatarProps = AvatarProps & {
  displayName?: string;
};
export default function ConversationAvatar({ displayName, ...props }: ConversationAvatarProps) {
  const src = props.src ?? '/broken';
  delete props.src;
  return <Avatar {...props} alt={displayName} src={src} />;
}

export const AvatarEditor = () => {
  const { t } = useTranslation();
  const [editionToolsIsOpen, setEditionToolsIsOpen] = useState(false);
  const [avatarHandler, setAvatarHandler] = useState<FileHandler | null>(null);
  const photoTakerHandler = useDialogHandler();

  const onFilesDrop = useCallback(
    (acceptedFiles: File[]) => {
      const fileHandler = new FileHandler(acceptedFiles[0]);
      setAvatarHandler(fileHandler);
      setEditionToolsIsOpen(false);
    },
    [setAvatarHandler, setEditionToolsIsOpen]
  );

  const {
    getRootProps,
    getInputProps,
    open: openFilePicker,
    isDragActive,
  } = useDropzone({
    accept: {
      'image/png': ['.png'],
      'image/jpeg': ['.jpeg', '.jpg'],
    },
    multiple: false,
    noClick: true,
    noKeyboard: true,
    onDrop: onFilesDrop,
  });

  const openPhotoTaker = useCallback(() => {
    photoTakerHandler.openDialog();
    setEditionToolsIsOpen(false);
  }, [photoTakerHandler]);

  const removeAvatar = useCallback(() => {
    setAvatarHandler(null);
    setEditionToolsIsOpen(false);
  }, []);

  const closeTools = useCallback(() => {
    setEditionToolsIsOpen(false);
  }, []);

  return (
    <Stack alignItems="center" position="relative" {...getRootProps()}>
      {isDragActive && <FileDragOverlay />}
      <input {...getInputProps()} />

      {/* Avatar preview */}
      <Stack height="150px" width="150px" position="relative">
        <Box sx={{ position: 'absolute', right: '19.89px', zIndex: 10 }}>
          <EditPictureButton onClick={() => setEditionToolsIsOpen(true)} sx={{ width: '30px', height: '30px' }} />
        </Box>
        <ConversationAvatar src={avatarHandler?.url} sx={{ width: '100%', height: '100%' }} />
      </Stack>

      {editionToolsIsOpen && (
        <Stack
          alignItems="center"
          justifyContent="center"
          position="absolute"
          height="100%"
          zIndex={10}
          spacing="26px"
          sx={{ backgroundColor: 'white' }}
        >
          <Typography variant="h3">{t('change_picture')}</Typography>
          <Stack direction="row" alignItems="center" spacing="9px">
            <TakePictureButton onClick={openPhotoTaker} />
            <UploadPictureButton onClick={openFilePicker} />
            {avatarHandler && <CancelPictureButton onClick={removeAvatar} />}
            <CloseButton onClick={closeTools} />
          </Stack>
        </Stack>
      )}

      <PhotoTakerDialog {...photoTakerHandler.props} onConfirm={setAvatarHandler} />
    </Stack>
  );
};

type PhotoTakerProps = {
  open: boolean;
  onClose: () => void;
  onConfirm: (photoHandler: FileHandler) => void;
};

const circleLengthPercentage = '50%';
const PhotoTakerDialog = ({ open, onClose, onConfirm }: PhotoTakerProps) => {
  const [isPaused, setIsPaused] = useState(false);
  const [video, setVideo] = useState<HTMLVideoElement | null>(null);
  const [stream, setStream] = useState<MediaStream | null>(null); // video.srcObject is not reliable
  const [permissionState, setPermissionState] = useState<PermissionState>('prompt');

  const videoRef = useCallback((node: HTMLVideoElement) => {
    setVideo(node);
  }, []);

  useEffect(() => {
    navigator.permissions
      // @ts-ignore
      // Firefox can't query camera permissions. Hopefully this will work soon.
      .query({ name: 'camera' })
      .then((permissionStatus) => {
        setPermissionState(permissionStatus.state);
        permissionStatus.onchange = () => {
          setPermissionState(permissionStatus.state);
          setStream(null);
        };
      })
      .catch(() => {
        // Expected behavior on Firefox
      });
  }, []);

  useEffect(() => {
    // Start stream when dialog opens, stop it when dialog closes
    if (open && !stream) {
      navigator.mediaDevices
        .getUserMedia({ video: true })
        .then((stream) => {
          setStream(stream);
          // This would be useless if we could query 'camera' permission on Firefox
          setPermissionState('granted');
        })
        .catch(() => {
          setStream(null);
          setPermissionState('denied');
        });
    }
    if (!open) {
      stream?.getTracks()?.forEach((track) => track.stop());
      setStream(null);
    }
  }, [open, stream, permissionState]);

  useEffect(() => {
    if (video) {
      if (stream) {
        video.srcObject = stream;
        video.play();
        setIsPaused(false);
      } else {
        video.srcObject = null;
      }
    }
  }, [video, stream]);

  const takePhoto = useCallback(() => {
    video?.pause();
    setIsPaused(true);
  }, [video]);

  const cancelPhoto = useCallback(() => {
    video?.play();
    setIsPaused(false);
  }, [video]);

  const savePhoto = useCallback(async () => {
    if (video) {
      // The PhotoTaker shows a circle in the middle of the camera's image.
      // We have to cut the picture, otherwise the avatar would be larger to what the user saw in boundaries of the circle
      // The circle's diameter is a percentage of the image's diagonal length
      const diagonalLength = Math.sqrt(Math.pow(video.offsetWidth, 2) + Math.pow(video.offsetHeight, 2));
      const avatarSize = diagonalLength * (parseInt(circleLengthPercentage) / 100);
      const xMargin = (video.offsetWidth - avatarSize) / 2;
      const yMargin = (video.offsetHeight - avatarSize) / 2;

      // @ts-ignore
      // Seems like Typescript does not know yet about OffscreenCanvas. Lets pretend it is HTMLCanvasElement for now
      const offscreenCanvas: HTMLCanvasElement = new OffscreenCanvas(avatarSize, avatarSize);
      const context = offscreenCanvas.getContext('2d');
      if (context) {
        context.setTransform(-1, 0, 0, 1, avatarSize, 0); // mirror
        context.drawImage(video, -xMargin, -yMargin, video.offsetWidth, video.offsetHeight);
        // @ts-ignore
        // convertToBlob is only on OffscreenCanvas, not HTMLCanvasElement, which has "toBlob" instead
        const blob: Blob = await offscreenCanvas.convertToBlob();
        const file = new File([blob], 'avatar.jpg', { type: 'image/jpeg' });
        const fileHandler = new FileHandler(file);
        onConfirm(fileHandler);
        onClose();
      }
    }
  }, [onConfirm, onClose, video]);

  if (permissionState === 'denied') {
    // TODO: UI needs to be improved
    return <InfosDialog title="" open={open} onClose={onClose} content={<CallPermissionDenied />} />;
  }

  return (
    <Dialog open={open} onClose={onClose}>
      <Stack>
        <Stack position="relative">
          <CornerCloseButton onClick={onClose} />
          {stream && <CircleMaskOverlay size={circleLengthPercentage} />}
          <video ref={videoRef} style={{ transform: 'rotateY(180deg)' /* mirror */ }} />
        </Stack>
        <Stack height="89px" direction="row" justifyContent="center" alignItems="center" spacing="10px">
          {isPaused ? (
            <>
              <RoundButton
                Icon={RefreshOutlined}
                aria-label="cancel photo"
                size="large"
                sx={{ fontSize: '40px' }}
                onClick={cancelPhoto}
              />
              <RoundButton
                Icon={Check}
                aria-label="save photo"
                size="large"
                sx={{ fontSize: '40px' }}
                onClick={savePhoto}
              />
            </>
          ) : (
            <RecordButton onClick={takePhoto} />
          )}
        </Stack>
      </Stack>
    </Dialog>
  );
};
