import React from 'react'
import PropTypes from 'prop-types'
import objectAssign from 'object-assign'
import { autobind } from 'core-decorators'
import { Progress } from 'antd'
import Dropzone from 'react-dropzone'
import Cropper from 'react-cropperjs-fork'
import Add from 'react-icons/lib/md/add'
import Delete from 'react-icons/lib/md/delete'
import ReactLoading from 'react-loading'
import { ModalContainer, ModalDialog } from 'react-modal-dialog-fork'
import { SortableContainer, SortableElement, arrayMove } from 'react-sortable-hoc'
import Tools from "../utils/tools";
import stylesheet from './Upload-Style'
import 'antd/dist/antd.css'

/** 
 * props 
 */
const propTypes = {
    value: PropTypes.array,
    defaultValue: PropTypes.array,
    sign: PropTypes.string,
    action: PropTypes.string,
    accept: PropTypes.string,
    name: PropTypes.string,
    headers: PropTypes.object,
    withCredentials: PropTypes.bool,
    disabled: PropTypes.bool,
    limit: PropTypes.number,
    maxSize: PropTypes.number,
    hasThumbnail: PropTypes.bool,
    hasDelete: PropTypes.bool,
    dropZoneProps: PropTypes.object,
    cropGetBase64Type: PropTypes.string,
    cropDisabled: PropTypes.bool,
    cropMinWidth: PropTypes.number,
    cropMinHeight: PropTypes.number,
    cropList: PropTypes.array,
    cropProps: PropTypes.object,
    sortDisabled: PropTypes.bool,
    sortableContainerProps: PropTypes.object,
    sortableElementProps: PropTypes.object,
    locale: PropTypes.object,
  
    formatter: PropTypes.func,
    beforeUpload: PropTypes.func,
    onSuccess: PropTypes.func,
    onError: PropTypes.func,
    onRemove: PropTypes.func,
    onChange: PropTypes.func,
    onShowUploadError: PropTypes.func,
    onSortEnd: PropTypes.func,
    onCrop: PropTypes.func,
    onCropError: PropTypes.func,
    onCropGetBase64Data: PropTypes.func,
    onShowCrop: PropTypes.func,
    onUploadClick: PropTypes.func,
};
  
const defaultProps = {
    value: null,
    defaultValue: null,
    sign: 'upload',
    action: '',
    accept: 'image/png, image/jpg, image/jpeg, image/gif, image/bmp',
    name: 'file',
    headers: null,
    withCredentials: true,
    disabled: false,
    limit: 8,
    maxSize: 10485760,
    hasThumbnail: true,
    hasDelete: true,
    dropZoneProps: {},
    cropGetBase64Type: 'load', // 1. load 下载图片获取base64 | 2. api 调用接口获取base64
    cropDisabled: false,
    cropMinWidth: 500,
    cropMinHeight: 500,
    cropList: [],
    cropProps: {},
    sortDisabled: false,
    sortableContainerProps: {},
    sortableElementProps: {},
    locale: {
      title: 'upload', 
      width: 'width',
      height: 'height',
      saveClose: 'save and close',
      addPicture: 'Add Picture',
      thumbnail: 'Thumbnail',
      mainImage: 'Main Image',
      uploadError: 'Upload Error',
      maxSizeError: 'Image size should not exceed 10M. Upload a different image.',
      cropError: 'Image clipping failed',
      duplicate: 'Duplicate Pictures',
      cropSizeTip: 'Dimensions should be between 500x500 and 2000x2000',
      uploadTips: ['Drag and drop pictures below to upload. Maximum 8 pictures, size between 500*500 and 2000*2000 px.'],
    },
  
    formatter: null,
    beforeUpload: null,
    onSuccess: null,
    onError: null,
    onRemove: null,
    onChange: null,
    onShowUploadError: null,
    onSortEnd: null,
    onCrop: null,
    onCropError: null,
    onCropGetBase64Data: null,
    onShowCrop: null,
    onUploadClick: null,
};

/** 
 * SortableElement
 */
const SortableItem = SortableElement(({
    value, 
    imgIndex, 
    imageDisabled,
    sortDisabled, 
    cropDisabled, 
    hasThumbnail,
    hasDelete,
    locale,
    onClick, 
    onView,
    setCropIndex, 
    onRemove}) =>
    <div className="upload-list-item" 
        style={objectAssign({}, stylesheet.styles.uploadListItem, sortDisabled ? {} : stylesheet.styles.cursorMove)} 
        onClick={(e) => {
            e.stopPropagation()
            !cropDisabled && onClick(value.data);
            !cropDisabled && setCropIndex(imgIndex);

            if (cropDisabled) {
                onView(value.data);
            }
        }} onMouseOut={ (e) => {
            let cur = e.currentTarget;
            cur.querySelector('.upload-tool') && (cur.querySelector('.upload-tool').style.opacity = 0);
        } } onMouseOver={ (e) => {
            let cur = e.currentTarget;
            cur.querySelector('.upload-tool') && (cur.querySelector('.upload-tool').style.opacity = 1);
        } }>
        <div className="upload-list-item-info" style={stylesheet.styles.uploadListItemInfo}>
            <div className="upload-list-item-thumbnail" style={stylesheet.styles.uploadListItemThumbnail}>
                <div style={objectAssign({}, {backgroundImage: `url("${value.data}")`, backgroundSize: 'contain'}, stylesheet.styles.uploadListItemThumbnailImage)}></div>
            </div>
            {imgIndex === 0 && hasThumbnail ?
            <span className="upload-thumbnail" style={stylesheet.styles.uploadThumbnail}>
                <span className="upload-tool-thumbnail" style={stylesheet.styles.uploadToolThumbnail} >
                    {locale.mainImage}
                </span>
            </span> : null}
            {hasDelete && !imageDisabled ? <span className="upload-tool" style={stylesheet.styles.uploadTool}>
                <span className="upload-tool-delete" style={stylesheet.styles.uploadToolDelete} onClick={(e) => {
                    e.stopPropagation();
                    onRemove(imgIndex)
                }}>
                    <Delete size={20} />
                </span>
            </span> : null}
        </div>
    </div>
);

/** 
 * SortableContainer
 */
const SortableList = SortableContainer(({
    value, 
    imageDisabled,
    sortDisabled, 
    cropDisabled, 
    hasThumbnail,
    hasDelete,
    locale,
    sortableElementProps, 
    onClick, 
    onView, 
    setCropIndex, 
    onRemove}) => {
  return (
    <div className="sort-container" style={stylesheet.styles.sortContainer}>
      <div>
        {value.map((item, index) => (
            <SortableItem 
                key={`item-${index}`} 
                index={index} 
                imgIndex={index} 
                value={item} 
                disabled={sortDisabled} 
                imageDisabled={imageDisabled}
                sortDisabled={sortDisabled} 
                cropDisabled={cropDisabled} 
                hasThumbnail={hasThumbnail}
                hasDelete={hasDelete}
                locale={locale}
                onClick={onClick} 
                onView={onView}
                setCropIndex={setCropIndex} 
                onRemove={onRemove} 
                {...sortableElementProps} />
        ))}
      </div>
    </div>
  );
});

@autobind
class Upload extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
        defaultValue: [],
        width: 0,
        height: 0,
        cropIndex: 0,
        cropImageBase64: '',
        cropPreview: '',
        isShowingCrop: false,
        viewImageUrl: '',
        isShowingView: false,
        showLoading: false,
        showToast: false,
        toastData: '',
        progressVisible: false,
        progressValue: 0,
    }
    this.uploadCount = 0
    this.errorFiles = [];
    this.uploadStack = []
    this.base64Map = {}
  }

  componentDidMount() {
    if(!this.props.value && this.props.defaultValue){
        this.setState({
            defaultValue: this.props.defaultValue
        })
    }
  }

  setCropIndex(index){
    this.setState({
        cropIndex: index
    })
  }

  async cacheBase64(img){
    let cropImageBase64;
    if(!this.base64Map[img]){
        if(this.props.cropGetBase64Type === 'load'){
            cropImageBase64 = await Tools.getImgBase64(img)
        } else{
            this.props.onCropGetBase64Data && (cropImageBase64 = await this.props.onCropGetBase64Data(img))
        }
    } else{
        cropImageBase64 = this.base64Map[img]
    }
    this.base64Map[img] = cropImageBase64
    return cropImageBase64
  }

  notifyShow(data = '', time = 5000) {
    this.setState({
        showToast: true,
        toastData: data,
    });
    if (this.toastTimer) {
        clearTimeout(this.toastTimer);
        this.toastTimer = null;
    }
    this.toastTimer = setTimeout(() => {
        this.setState({
            showToast: false,
            toastData: '',
        });
    }, time)
  }

  checkUploadError() {
    this.uploadCount--
    if(this.uploadCount === 0){
        this.showProgress(false, (this.cacheUploadCount - this.uploadCount) * (100 / this.cacheUploadCount))
        if(this.errorFiles.length > 0){
            if (this.props.onShowUploadError) {
                this.props.onShowUploadError(this.errorFiles.slice(0));
            } else {
                // 展示错误，关闭后清空错误栈
                const errorShow = this.errorFiles.map((errorItem, errorIndex) => {
                    return <div key={errorIndex} style={stylesheet.styles.errorContainer}>
                        <img src={errorItem.file} style={stylesheet.styles.errorImage} height="40" width="40" />
                        <span style={stylesheet.styles.errorMsg}>{errorItem.msg}</span>
                    </div>
                })
                this.notifyShow(errorShow, 5000);
            }
            this.errorFiles = [];
        }
    } else {
        this.showProgress(true, (this.cacheUploadCount - this.uploadCount) * (100 / this.cacheUploadCount))
    }
  }

  onDrop(accepted, rejected){
    let { locale } = this.props
    locale = objectAssign({}, defaultProps.locale, locale)
    // 最大上传数
    // accepted.length > this.props.limit && (accepted.length = this.props.limit)
    this.uploadCount += (accepted.length + rejected.length)
    this.cacheUploadCount = this.uploadCount;
    this.showProgress(true, (this.cacheUploadCount - this.uploadCount) * (100 / this.cacheUploadCount))
    accepted.map(async (file) => {
        const result = this.props.beforeUpload ? await this.props.beforeUpload(file) : true
        if(result){
            Tools.upload(file, {
                withCredentials: this.props.withCredentials,
                name: this.props.name,
                headers: this.props.headers,
            }, async (res) => {
                if(res.code){
                    if(this.uploadStack.indexOf(`${this.props.sign}-${res.data}`) < 0){
                        if(!this.props.value){
                            let value = this.state.defaultValue;
                            let data = {...res};
                            data.name = file.name;
                            data.size = file.size;
                            value.push(data);
                            this.setState({
                                defaultValue: value
                            })
                        }
                        this.props.onSuccess && this.props.onSuccess(res, file)
                        this.props.onChange && this.props.onChange({
                            type: 'success',
                            index: -1,
                            res: res,
                            value: file,
                        })
                    } else{
                        this.errorFiles.push({
                            msg: locale.duplicate,
                            file: await Tools.getFileBase64(file)
                        })
                    }
                } else{
                    this.props.onError && this.props.onError(res, file)
                    this.props.onChange && this.props.onChange({
                        type: 'error',
                        index: -1,
                        res: res,
                        value: file,
                    })
                    this.errorFiles.push({
                        msg: res.error || locale.uploadError,
                        file: await Tools.getFileBase64(file)
                    })
                }
                this.checkUploadError();
            }, this.props.formatter, this.props.action)
        } else{
            this.errorFiles.push({
                msg: locale.uploadError,
                file: await Tools.getFileBase64(file)
            })
            this.checkUploadError();
        }
    })
    rejected.map(async (file) => {
        this.props.onError && this.props.onError({}, file)
        this.props.onChange && this.props.onChange({
            type: 'error',
            index: -1,
            res: {},
            value: file,
        })
        this.errorFiles.push({
            msg: locale.maxSizeError,
            file: await Tools.getFileBase64(file)
        })
        this.checkUploadError();
    })
  }

  showLoading(show) {
    this.setState({
        showLoading: show,
    })
    if(this.showLoadingTimer){
        clearTimeout(this.showLoadingTimer)
        this.showLoadingTimer = null
    }
    this.showLoadingTimer = setTimeout(() => {
        this.setState({
            showLoading: false,
        })
    }, 60 * 1000)
  }

  showProgress(show, progress) {
    if (!show) {
        this.setState({
            progressVisible: true,
            progressValue: 100,
        })
        setTimeout(() => {
            this.setState({
                progressVisible: false,
                progressValue: 0,
            })
        }, 2000)
    } else {
        this.setState({
            progressVisible: show,
            progressValue: progress,
        })
    }
    if(this.showProgressTimer){
        clearTimeout(this.showProgressTimer)
        this.showProgressTimer = null
    }
    this.showProgressTimer = setTimeout(() => {
        this.setState({
            progressVisible: false,
            progressValue: 0,
        })
    }, 60 * 1000)
  }

  async showCrop(img) {
    this.showLoading(true)
    let cropImageBase64 = await this.cacheBase64(img)
    this.setState({
        isShowingCrop: true,
        cropImageBase64: cropImageBase64,
    })
    this.showLoading(false)
    if (this.props.cropList) {
        this.props.onShowCrop && this.props.onShowCrop('list');
    } else {
        this.props.onShowCrop && this.props.onShowCrop('init');
    }
  }

  closeCrop() {
    this.setState({isShowingCrop: false})
  }

  showView(img) {
    this.setState({
        isShowingView: true,
        viewImageUrl: img
    })
  }

  closeView() {
      this.setState({isShowingView: false})
  }

  onSortEnd({oldIndex, newIndex}){
    let value = this.props.value;
    if(!this.props.value){
        value = this.state.defaultValue;
    }
    this.value = arrayMove(value, oldIndex, newIndex)
    if(!this.props.value){
        this.setState({
            defaultValue: this.value
        })
    }
    this.props.onSortEnd && this.props.onSortEnd(this.value)
    this.props.onChange && this.props.onChange({
        type: 'sort',
        index: -1,
        res: {},
        value: this.value,
    })
  }

  _crop(data) {
    let base64Image = this.refs.cropper.getCroppedCanvas().toDataURL();
    let {width, height} = data.detail;
    width = Math.round(width);
    height = Math.round(height);
    if(width <= this.props.cropMinWidth){
        if(!this.do){
            this.do = true;
            this.refs.cropper.setData({
               x: 0,
            })
        }
    }
    if(width < this.props.cropMinWidth){
       width = this.props.cropMinWidth
       this.refs.cropper.setData({
           width: width,
        })
    }
    if(height < this.props.cropMinHeight){
       height = this.props.cropMinHeight
       this.refs.cropper.setData({
           height: height,
        })
    }
    this.setState({
       width: width,
       height: height,
       cropPreview: base64Image,
    })
  }

  onCrop(){
    let { locale } = this.props
    locale = objectAssign({}, defaultProps.locale, locale)
    let base64Image = this.refs.cropper.getCroppedCanvas().toDataURL();
    let file = Tools.getBlobBydataURI(base64Image, `image/png`);
    file.name = `crop-${+new Date()}.png`;
    this.showLoading(true)
    Tools.upload(file, {
        withCredentials: this.props.withCredentials,
        name: this.props.name,
        headers: this.props.headers,
    }, (res) => {
        if(res.code){
            if(!this.props.value){
                let value = this.state.defaultValue;
                let data = {...res};
                data.name = file.name;
                data.size = file.size;
                value[this.state.cropIndex] = data;
                this.setState({
                    defaultValue: value
                })
            }
            this.closeCrop()
            this.props.onCrop && this.props.onCrop(res, this.state.cropIndex, {
                width: this.state.width,
                height: this.state.height,
            })
        } else{
            this.notifyShow(res.error || locale.cropError, 5000);
            this.props.onCropError && this.props.onCropError(res, this.state.cropIndex)
        }
        this.props.onChange && this.props.onChange({
            type: 'crop',
            index: this.state.cropIndex,
            res: res,
            value: [],
        })
        this.showLoading(false)
    }, this.props.formatter, this.props.action)
  }

  onRemove(index){
    let removeValue = ''
    if(!this.props.value){
        let value = this.state.defaultValue
        removeValue = value[index].data
        value.splice(index, 1)
        this.setState({
            defaultValue: value
        })
    } else{
        removeValue = this.props.value[index].data
    }
    this.props.onRemove && this.props.onRemove(index)
    this.props.onChange && this.props.onChange({
        type: 'remove',
        index: index,
        res: {},
        value: [],
    })
    if(this.uploadStack && this.uploadStack.length){
        let removeIndexInStack = this.uploadStack.indexOf(`${this.props.sign}-${removeValue}`)
        this.uploadStack.splice(removeIndexInStack, 1)
    }
  }

  render() {
    let { value, 
          locale 
        } = this.props
    let { defaultValue = [], 
          showLoading = false, 
          cropPreview = '', 
          showToast = false, 
          toastData = '',
          progressVisible = false,
          progressValue = 0,
        } = this.state
    locale = objectAssign({}, defaultProps.locale, locale)
    value && value.length > this.props.limit && (value.length = this.props.limit)
    defaultValue.length > this.props.limit && (defaultValue.length = this.props.limit)
    let renderValue = value && value.length ? value : defaultValue

    let signStack = []
    this.uploadStack.map((uItem) => {
        if (uItem.indexOf(this.props.sign) !== 0) {
            signStack.push(uItem)
        }
    })
    renderValue.map((item) => {
        signStack.push(`${this.props.sign}-${item.data}`)
    })
    this.uploadStack = signStack.slice(0)
    

    return (
        <div className="dropzone" style={stylesheet.styles.dropZone}>
          {showLoading ?
          <div className="loading" style={stylesheet.styles.loading}>
            <ReactLoading style={stylesheet.styles.reactLoading} delay={0} type="cubes" color="#00BCD4" height={200} width={100} />
          </div> : null}
          {showToast ?
          <div style={stylesheet.styles.toast}>
              {toastData}
          </div> : null}
          <div className="upload-tip">
                {locale.uploadTips.map((item, index) => {
                    return <span key={index} className="upload-tip-item" style={stylesheet.styles.uploadTipItem}>{item}</span>
                })}
          </div>
          <div className="progress-container" style={stylesheet.styles.progressContainer}>
                {progressVisible ? <Progress percent={progressValue} /> : null}
          </div>
          <SortableList 
            value={renderValue} 
            axis={'x'} 
            helperClass="image-sort-helper" 
            imageDisabled={this.props.disabled}
            sortDisabled={this.props.sortDisabled} 
            cropDisabled={this.props.cropDisabled}
            hasThumbnail={this.props.hasThumbnail}
            hasDelete={this.props.hasDelete}
            locale={locale}
            onClick={this.showCrop} 
            onView={this.showView}
            onSortEnd={this.onSortEnd} 
            setCropIndex={this.setCropIndex} 
            onRemove={this.onRemove} 
            sortableElementProps={this.props.sortableElementProps} 
            {...this.props.sortableContainerProps} />
          {renderValue.length < this.props.limit ?
          <Dropzone
            className="drop-upload"
            style={stylesheet.styles.dropUpload}
            disabled={this.props.disabled}
            accept={this.props.accept}
            maxSize={this.props.maxSize}
            acceptStyle={{
                background: "#DDD",
                border: "3px solid #666"
            }}
            onDrop={this.onDrop}
            onClick={this.props.onUploadClick}
            {...this.props.dropZoneProps}
          >
            <div style={stylesheet.styles.uploadBox}>
                <span className="upload-select" style={stylesheet.styles.uploadSelect}>
                    <Add size={60} />
                    <div className="upload-text" style={stylesheet.styles.uploadText}>{this.props.locale.addPicture}</div>
                </span>
            </div>
          </Dropzone> : null }
            {
                this.state.isShowingCrop &&
                <ModalContainer className="modal-container" zIndex={9999} width={100} onClose={this.closeCrop}>
                <ModalDialog className="modal-dialog" onClose={this.closeCrop}>
                <div className="crop-container" style={stylesheet.styles.cropContainer}>
                    <div className="upload-title" style={stylesheet.styles.uploadTitle}>
                        {locale.title}
                    </div>
                    {this.props.cropList && this.props.cropList.length ?
                    <div className="crop-list" style={stylesheet.styles.cropList}>
                        {this.props.cropList.map((cropItem, cropIndex) => {
                            let selectedCrop = {}
                            if(this.state.cropIndex === cropIndex){
                                selectedCrop = stylesheet.styles.selectedCrop
                            }
                            return <div key={cropIndex} className="crop-item" style={objectAssign({} ,stylesheet.styles.cropItem, selectedCrop)} onClick={(e) => {
                                    e.stopPropagation()
                                    !this.props.cropDisabled && this.showCrop(cropItem);
                                    !this.props.cropDisabled && this.setCropIndex(cropIndex);
                                }}>
                                <img src={cropItem} style={stylesheet.styles.cropImg} />
                            </div>
                        })}
                    </div> : null}
                    <div className="crop-tips" style={stylesheet.styles.cropTips}>
                        <span className="crop-label" style={stylesheet.styles.cropLabel}>{locale.cropSizeTip}</span>
                    </div>
                    <div className="tips" style={stylesheet.styles.tips}>
                        <span className="label" style={stylesheet.styles.label}>{locale.width}:</span> {this.state.width}
                        <span className="label-height" style={stylesheet.styles.labelHeight}>{locale.height}:</span> {this.state.height}
                    </div>
                    <div className="crop-main" style={stylesheet.styles.cropMain}>
                        <div className="crop-area" style={stylesheet.styles.cropArea}>
                            <Cropper
                                style={{ height: '100%', width: '100%' }}
                                viewMode={1}
                                autoCropArea={0.8}
                                aspectRatio={1 / 1}
                                preview='.img-preview'
                                guides={false}
                                center={false}
                                background={false}
                                zoomable={true}
                                src={this.state.cropImageBase64}
                                ref='cropper'
                                crop={this._crop} {...this.props.cropProps} />
                        </div>
                        <div className="preview-area" style={stylesheet.styles.previewArea}>
                            <div style={stylesheet.styles.imgPreview}>
                                <div className="img-preview" style={stylesheet.styles.imgPreview80}>
                                    <img src={cropPreview} />
                                </div>
                                <span style={stylesheet.styles.cropSize}>80*80</span>
                            </div>
                            <div style={stylesheet.styles.imgPreview}>
                                <div className="img-preview" style={stylesheet.styles.imgPreview60}>
                                    <img src={cropPreview} />
                                </div>
                                <span style={stylesheet.styles.cropSize}>60*60</span>
                            </div>
                            <div style={stylesheet.styles.imgPreview}>
                                <div className="img-preview" style={stylesheet.styles.imgPreview40}>
                                    <img src={cropPreview} />
                                </div>
                                <span style={stylesheet.styles.cropSize}>40*40</span>
                            </div>
                        </div>
                    </div>
                    
                    <div className="action" style={stylesheet.styles.action}>
                        <div className="save-close" style={stylesheet.styles.saveClose} onClick={this.onCrop}>{locale.saveClose}</div>
                    </div>
                </div>
                </ModalDialog>
                </ModalContainer>
            }
            {
                this.state.isShowingView &&
                <ModalContainer className="modal-container" zIndex={9999} width={100} onClose={this.closeView}>
                <ModalDialog className="modal-dialog" style={stylesheet.styles.modalDialog} onClose={this.closeCrop}>
                    <img height="500" style={stylesheet.styles.previewImage} src={this.state.viewImageUrl} />
                </ModalDialog>
                </ModalContainer>
            }
        </div>
    )
  }
}

Upload.propTypes = propTypes;
Upload.defaultProps = defaultProps;

export { Upload as PureUpload }
export default Upload