import React, { Fragment, useRef, useEffect, useState } from 'react'
import { string, number, func, bool, node, oneOfType } from 'prop-types'

import classNames from 'classnames'
import DemioIcon from '../Icon/Icon'
import DemioInfo from '../DemioInfo/DemioInfo'
import Cropper from 'react-easy-crop'
import Button from '../Button/Button'
import { Upload, Progress, Dropdown, Menu, Modal, Slider } from 'antd'
const { Dragger } = Upload
import {
  calculateSizeLimit, validateImageDimensions, isExtensionExecutable,
  isMimeExecutable, checkMIMEType, getAcceptedMIMETypes
} from './helpers'
import './DemioUpload.less'

const propTypes = {
  uploadRequest: func.isRequired,
  sizeLimit: string.isRequired,
  onDeleteFile: func,
  acceptedExtensions: string,
  className: string,
  extraFileInfo: oneOfType([string, bool]),
  filePreview: string,
  uploadProgress: number,
  beforeUpload: func,
  onCancelUpload: func,
  dimensionsLimit: string,
  resizeWidth: number,
  uploading: bool,
  type: string,
  fileURL: string,
  fileName: string,
  showDeleteOption: bool,
  customMenuOption: node,
  errorMessage: oneOfType([string, bool]),
  customDescriptionText: oneOfType([string, node]),
  cropRatio: number,
  minCropLength: number,
  cropModalTitle: string,
  onCancelCrop: func,
  confirmCropText: string,
  cancelCropText: string,
  onConfirmCrop: func,
  loading: bool
}

const defaultProps = {
  className: '',
  extraFileInfo: '',
  filePreview: '',
  uploadProgress: 0,
  acceptedExtensions: '',
  type: 'File',
  uploading: false,
  showDeleteOption: true,
  customMenuOption: null,
  errorMessage: '',
  customDescriptionText: null,
  showDownloadOption: true,
  cropRatio: 1,
  minCropLength: 100,
  cropModalTitle: 'Crop & Upload Image',
  confirmCropText: 'CROP IMAGE',
  cancelCropText: 'NO, NEVER MIND',
  loading: false
}

const DemioUpload = ({
  acceptedExtensions, uploadRequest, beforeUpload,
  filePreview, onCancelUpload, dimensionsLimit,
  className, sizeLimit, uploadProgress, uploading, type,
  fileURL, extraFileInfo, onDeleteFile, fileName,
  showDeleteOption, customMenuOption, errorMessage,
  customDescriptionText, onError, showDownloadOption,
  processingPercent, cropRatio, minCropLength,
  cropModalTitle, onCancelCrop, confirmCropText,
  cancelCropText, onConfirmCrop, loading, resizeWidth, originalResizeWidth
}) => {
  const [showCropperModal, toggleCropperModal] = useState(false)
  const [crop, setCrop] = useState({ x: 0, y: 0 })
  const [zoom, setZoom] = useState(1)
  const [maxZoom, setMaxZoom] = useState(3)
  const [croppedAreaPixels, setCroppedAreaPixels] = useState(null)
  const [isImageCropped, setIsCropped] = useState(true)
  const [isCropProcessing, setCropProgressing] = useState(false)

  const uploadDropdownRef = useRef(null)
  const prevFileURL = useRef(fileURL)

  const isImageCrop = type === 'Image-Crop'
  const isImage = isImageCrop || type === 'Image'
  const typeText = type === 'Image-Crop' ? 'Image' : type

  useEffect(() => {
    // open cropper modal immediately after the image is uploaded
    if (isImageCrop && !isImageCropped && prevFileURL.current !== fileURL) {
      if (fileURL) toggleCropperModal(true)
      prevFileURL.current = fileURL
    }
  }, [fileURL])

  const handleOnError = (message) => {
    onError(message)
  }

  const checkisExecutable = (file) => {
    if (isMimeExecutable(file.type) || isExtensionExecutable(file.name)) {
      handleOnError(`File type is not supported. Compress/zip executable files to upload them.`)
      return false
    }

    return true
  }

  const checkFileSizeLimit = ({ size }) => {
    const fileSizeLimit = calculateSizeLimit(sizeLimit)

    if (size > fileSizeLimit) {
      handleOnError(`Exceeds maximum size limit of ${sizeLimit}.`)
      return false
    }

    return true
  }

  const checkDimensionLimit = (file) => new Promise((resolve, reject) => {
    if (file.type === 'image/svg+xml') return resolve()

    // This is undoubtedly a very cool way to get the dimension limits from a simple string "1000x500"
    // but is it over-complicating things a little bit? I think both can be true
    const [minWidth, minHeight] = dimensionsLimit.split('x')
    const minWidthNumber = Number(minWidth)
    const minHeightNumber = Number(minHeight)

    validateImageDimensions(file, { minWidth: minWidthNumber, minHeight: minHeightNumber }, ({ error }) => {
      if (error) {
        handleOnError(`Minimum required dimensions are ${dimensionsLimit}.`)
        return reject()
      }

      return resolve()
    })
  })

  const checkFileOnDrop = (e) => {
    const file = e.dataTransfer.files[0]
    e.preventDefault()
    e.stopPropagation()

    if (!checkMIMEType(file.type, acceptedExtensions, file.name)) {
      handleOnError(`File type is not supported.`)
      return false
    }
  }

  const beforeUploadRequest = (file, fileList) => {
    handleOnError('')
    if (isImageCrop) setIsCropped(false)

    if (!checkMIMEType(file.type, acceptedExtensions, file.name)) {
      handleOnError(`File type is not supported.`)
      return false
    }

    if (dimensionsLimit) {
      return checkDimensionLimit(file)
        .then(() => {
          if (!checkisExecutable(file)) throw 'Executable not allowed'
          if (!checkFileSizeLimit(file)) throw 'File size exceded'

          if (beforeUpload) beforeUpload(file, fileList)
        })
    }

    if (!checkisExecutable(file)) return false
    if (!checkFileSizeLimit(file)) return false

    if (beforeUpload) beforeUpload(file, fileList)

    return true
  }

  const customRequest = async (data) => {
    let theData = data
    const isResizeOnly = resizeWidth && !isImageCrop
    if (isResizeOnly) {
      theData = await handleResizeImage(data, { width: resizeWidth })
    }
    uploadRequest(theData)
  }

  const handleCancelCrop = () => {
    onCancelCrop && onCancelCrop()
    toggleCropperModal(false)
  }

  const resizeImage = (img, width) => {
    const canvas = document.createElement('canvas')
    const ctx = canvas.getContext('2d')
    
    canvas.width = width
    canvas.height = canvas.width * (img.height / img.width)
    ctx.imageSmoothingQuality = "high"
    ctx.imageSmoothingEnabled = true
    ctx.fillStyle = "#FFF"
    ctx.fillRect(0, 0, canvas.width, canvas.height)
    ctx.drawImage(img, 0, 0, canvas.width, canvas.height)
    return canvas
  }
  
  const getBinaryFileFromCanvas = canvas => new Promise(resolve => {
    canvas.toBlob(file => {
      resolve(new File([file], "cropped-" + Date.now() + ".jpg", { type: "image/jpeg" }))
    }, 'image/jpeg', 0.96)
  })

  const handleCropImage = () => {
    const { x: xOffset, y: yOffset, width: cropWidth, height: cropHeight } = croppedAreaPixels
    const newImage = new Image()
    newImage.setAttribute('crossorigin', 'anonymous')
    setCropProgressing(true)

    newImage.onload = async () => {
      // create a canvas that will hold the new cropped image
      const croppedImageCanvas = document.createElement('canvas')
  
      // set it to the new cropped size
      croppedImageCanvas.width = cropWidth
      croppedImageCanvas.height = cropHeight

      // draw the new cropped image on the canvas
      const ctx = croppedImageCanvas.getContext('2d')
      ctx.imageSmoothingQuality = "high"
      ctx.imageSmoothingEnabled = true
      ctx.fillStyle = "#FFF"
      ctx.fillRect(0, 0, croppedImageCanvas.width, croppedImageCanvas.height)
      ctx.drawImage(newImage, 0 - xOffset, 0 - yOffset)

      const resized = resizeWidth && cropWidth >= resizeWidth && await getBinaryFileFromCanvas(resizeImage(croppedImageCanvas, resizeWidth))
      const original = originalResizeWidth && await getBinaryFileFromCanvas(resizeImage(croppedImageCanvas, originalResizeWidth))
      
      await onConfirmCrop({ original, resized })
      
      setIsCropped(true)
      toggleCropperModal(false)
      setCropProgressing(false)
    }

    newImage.src = fileURL
  }

  const handleResizeImage = ({ file }, { width }) => new Promise (resolve => {
    const newFile = new FileReader()
    
    newFile.onload = async () => {
      const newImage = new Image()
      newImage.setAttribute('crossorigin', 'anonymous')
      newImage.onload = async () => {
        const resultCanvas = resizeImage(newImage, width)
        resolve({ file: await getBinaryFileFromCanvas(resultCanvas) })
      }
      newImage.src = newFile.result
    }
    newFile.readAsDataURL(file)
  })

  const renderError = () => {
    if (!errorMessage) return

    return (
      <DemioInfo
        message={errorMessage}
        style={{ marginBottom: 8 }}
        isError
      />
    )
  }

  const renderCropperModal = () => (
    <Modal
      title={cropModalTitle}
      wrapClassName="demio-upload-modal"
      width={560}
      visible={showCropperModal}
      onCancel={handleCancelCrop}
      footer={null}
    >
      <div className="demio-upload-modal-top">
        <div className="demio-upload-cropper-container">
          <Cropper
            image={fileURL}
            crop={crop}
            zoom={zoom}
            aspect={cropRatio}
            onCropChange={setCrop}
            onZoomChange={setZoom}
            showGrid={false}
            maxZoom={maxZoom}
            onMediaLoaded={({ naturalHeight, naturalWidth }) => {
              if (!minCropLength) return
              // Adapt max zoom based on media size to fit min length
              const minImageSize = Math.min(naturalHeight, naturalWidth)

              setMaxZoom(minImageSize / minCropLength)
            }}
            onCropComplete={(croppedArea, croppedAreaPixels) => setCroppedAreaPixels(croppedAreaPixels)}
          />
        </div>
        <Slider
          step={0.1}
          disabled={maxZoom <= 1}
          onChange={(value) => setZoom(value)}
          min={1}
          max={maxZoom}
          tooltipVisible={false}
          value={zoom}
        />
      </div>
      <div className="demio-upload-modal-bottom">
        <Button title={cancelCropText} onClick={handleCancelCrop} white disabled={isCropProcessing} />
        <Button title={confirmCropText} onClick={handleCropImage} loading={isCropProcessing} />
      </div>
    </Modal>
  )

  const renderDragger = () => {
    const acceptedExtensionsFormatted = acceptedExtensions.split(',').join(' ')
    const acceptAllExtensions = !acceptedExtensionsFormatted
    const extenstionsMessage = acceptAllExtensions ? 'All' : acceptedExtensionsFormatted
    const acceptedMIMETypes = getAcceptedMIMETypes(acceptedExtensions, true)

    return (
      <div onDrop={checkFileOnDrop}>
        <Dragger
          multiple={false}
          showUploadList={false}
          beforeUpload={beforeUploadRequest}
          customRequest={customRequest}
          accept={acceptedMIMETypes}
          className={classNames({
            'demio-upload-dragger': true,
            '--hide': loading || ((uploading || fileURL) && !(isImageCrop && showCropperModal))
          })}
        >
          <div className="demio-upload-info">
            <div className="demio-upload-dragger-message">
              <DemioIcon type="Add" width={24} />
              Upload {typeText}
            </div>
            <div className="demio-upload-dragger-description">
              <strong>{extenstionsMessage}</strong>
              {acceptAllExtensions && ' (except executables)'} up to <strong>{sizeLimit}</strong>
              {customDescriptionText}
            </div>
          </div>
          <div className="demio-upload-drop-message">Drop it like it’s hot! 🔥</div>
        </Dragger>
      </div>
    )
  }

  const renderProgress = (progress) => (
    <Fragment>
      <Progress
        type="circle"
        percent={progress}
        showInfo={false}
        strokeColor="#02BF6F"
        trailColor="transparent"
        width={12}
      />
      <span className="demio-upload-progress">
        {progress}%
      </span>
    </Fragment>
  )

  const renderDropdown = () => (
    <Dropdown
      overlay={
        <Menu className="demio-upload-options-menu">
          {!uploading && customMenuOption}
          {(!uploading && fileURL && showDownloadOption || loading) && (
            <Menu.Item
              key="download"
              onClick={() => window.open(fileURL, '_blank')}
              disabled={loading}
            >
              <DemioIcon type="Download" width={16} fill="#5D676B" /> Download {typeText}
            </Menu.Item>
          )}
          {(!uploading && showDeleteOption || loading) && (
            <Menu.Item
              className="--red"
              key="delete"
              onClick={onDeleteFile}
              disabled={loading}
            >
              <DemioIcon type="Trash" width={16} fill="#EE4C3B" /> Delete {typeText}
            </Menu.Item>
          )}
          {uploading && (
            <Menu.Item
              className="--red"
              key="cancel"
              onClick={onCancelUpload}
            >
              Cancel Upload
            </Menu.Item>
          )}
        </Menu>
      }
      trigger={['click']}
      placement="bottomRight"
      getPopupContainer={() => uploadDropdownRef.current}
    >
      <div className="demio-upload-options-button" ref={uploadDropdownRef}>
        <DemioIcon type="Ellipsis" width={16} />
      </div>
    </Dropdown>
  )

  const renderLoadingState = () => (
    <div className="demio-upload-info-container --loading">
      <div className="demio-upload-loading-container">
        {renderProgress(75)}
      </div>
      {renderDropdown()}
    </div>
  )

  const renderUploadInfo = () => {
    if (!loading && !fileURL && !uploading || (isImageCrop && showCropperModal)) return null
    const theFileName = fileName || fileURL
    // Only show file info for Image when uploading
    const showFileInfo = isImage ? uploading : true
    const showFilePreview = filePreview && !uploading

    if (loading) return renderLoadingState()

    return (
      <div className={classNames({
        'demio-upload-info-container': true,
        '--image-only': showFilePreview && isImage
      })}>
        {showFilePreview && (
          <div className="demio-upload-preview">
            {processingPercent >= 0 && (
              <div className="demio-upload-processing">
                {renderProgress(processingPercent)}
              </div>
            )}
            <img src={filePreview} />
          </div>
        )}
        <div className="demio-upload-bottom">
          {uploading && uploadProgress >= 0 && (
            <div style={{ marginLeft: 8, display: 'flex' }}>
              {renderProgress(uploadProgress)}
            </div>
          )}
          {showFileInfo && (
            <div className="demio-upload-file-info">
              <div className="file-name">{theFileName}</div>
              {!uploading && extraFileInfo && (
                <div className="file-info">
                  {extraFileInfo}
                </div>
              )}
            </div>
          )}
          {renderDropdown()}
        </div>
      </div>
    )
  }

  return (
    <>
      {isImageCrop && renderCropperModal()}
      <div className={classNames({
        'demio-upload': true,
        [className]: className
      })}>
        {renderError()}
        {renderDragger()}
        {renderUploadInfo()}
      </div>
    </>
  )
}

DemioUpload.propTypes = propTypes
DemioUpload.defaultProps = defaultProps

export default DemioUpload