/* @flow */
import React, { Component } from "react";
import PropTypes from "prop-types";
import fetch from "isomorphic-fetch";
import autobind from "autobind-decorator";
import classnames from "classnames";
import Dropzone from "react-dropzone";
import Button from "react-progress-button-for-images-uploader";
import "babel-core/register";
import "babel-polyfill";

class ImagesUploader extends Component {
	/* eslint-disable react/sort-comp */
	state: {
		imagePreviewUrls: Array<string>,
		loadState: string,
		optimisticPreviews: Array<string>,
		displayNotification: boolean,
	};
	input: ?HTMLInputElement;

	static propTypes = {
		url: PropTypes.string.isRequired,
		dataName: PropTypes.string,
		headers: PropTypes.object,
		classNamespace: PropTypes.string,
		inputId: PropTypes.string,
		label: PropTypes.string,
		images: PropTypes.array,
		disabled: PropTypes.bool,
		onLoadStart: PropTypes.func,
		onLoadEnd: PropTypes.func,
		deleteImage: PropTypes.func,
		clickImage: PropTypes.func,
		optimisticPreviews: PropTypes.bool,
		multiple: PropTypes.bool,
		image: PropTypes.string,
		notification: PropTypes.string,
		max: PropTypes.number,
		color: PropTypes.string,
		disabledColor: PropTypes.string,
		borderColor: PropTypes.string,
		disabledBorderColor: PropTypes.string,
		notificationBgColor: PropTypes.string,
		notificationColor: PropTypes.string,
		deleteElement: PropTypes.oneOfType([
			PropTypes.string,
			PropTypes.element,
		]),
		plusElement: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
		classNames: PropTypes.shape({
			container: PropTypes.string,
			label: PropTypes.string,
			deletePreview: PropTypes.string,
			loadContainer: PropTypes.string,
			dropzone: PropTypes.string,
			pseudobutton: PropTypes.string,
			pseudobuttonContent: PropTypes.string,
			imgPreview: PropTypes.string,
			fileInput: PropTypes.string,
			emptyPreview: PropTypes.string,
			filesInputContainer: PropTypes.string,
			notification: PropTypes.string,
		}),
		styles: PropTypes.shape({
			container: PropTypes.object,
			label: PropTypes.object,
			deletePreview: PropTypes.object,
			loadContainer: PropTypes.object,
			dropzone: PropTypes.object,
			pseudobutton: PropTypes.object,
			pseudobuttonContent: PropTypes.object,
			imgPreview: PropTypes.object,
			fileInput: PropTypes.object,
			emptyPreview: PropTypes.object,
			filesInputContainer: PropTypes.object,
			notification: PropTypes.object,
		}),
	};

	static defaultProps = {
		dataName: "imageFiles",
		headers: {},
		classNames: {},
		styles: {},
		multiple: true,
		color: "#142434",
		disabledColor: "#bec3c7",
		borderColor: "#a9bac8",
		disabledBorderColor: "#bec3c7",
		notificationBgColor: "rgba(0, 0, 0, 0.3)",
		notificationColor: "#fafafa",
		classNamespace: "iu-",
	};

	constructor(props: Object) {
		super(props);
		let imagePreviewUrls = [];
		if (this.props.images && this.props.multiple !== false) {
			imagePreviewUrls = this.props.images || [];
		}
		if (this.props.image && this.props.multiple === false) {
			imagePreviewUrls = [this.props.image];
		}
		this.state = {
			imagePreviewUrls,
			loadState: "",
			optimisticPreviews: [],
			displayNotification: false,
		};
		this.input = null;
	}
	/* eslint-enable react/sort-comp */

	componentWillMount() {
		// support SSR rendering.
		// we should not use document on server, so just omit
		// these calls
		if (typeof document !== "undefined") {
			document.addEventListener(
				"dragover",
				(event) => {
					// prevent default to allow drop
					event.preventDefault();
				},
				false
			);
			document.addEventListener(
				"drop",
				(event) => {
					// prevent default to allow drop
					event.preventDefault();
				},
				false
			);
		}
	}

	componentWillReceiveProps(nextProps: Object) {
		if (
			!this.props.images &&
			nextProps.images &&
			nextProps.multiple !== false
		) {
			this.setState({
				imagePreviewUrls: nextProps.images,
			});
		}
		if (
			!this.props.image &&
			nextProps.image &&
			nextProps.multiple === false
		) {
			this.setState({
				imagePreviewUrls: [nextProps.image],
			});
		}
	}

	@autobind
	clickImage(key: number, url: string) {
		const clickImageCallback = this.props.clickImage;
		if (clickImageCallback && typeof clickImageCallback === "function") {
			clickImageCallback(key, url);
		}
	}

	@autobind
	deleteImage(key: number, url: string) {
		if (!this.props.disabled) {
			const imagePreviewUrls = this.state.imagePreviewUrls;
			imagePreviewUrls.splice(key, 1);
			this.setState({
				imagePreviewUrls,
			});
			if (
				this.props.deleteImage &&
				typeof this.props.deleteImage === "function"
			) {
				this.props.deleteImage(key, url);
			}
		}
	}

	@autobind
	buildPreviews(
		urls: Array<string>,
		optimisticUrls?: Array<string>,
		inButton?: boolean
	) {
		const {
			classNamespace,
			disabled,
			classNames,
			styles,
			color,
			disabledColor,
			borderColor,
			disabledBorderColor,
			notificationBgColor,
			notificationColor,
			deleteElement,
			plusElement,
		} = this.props;

		if (
			(!urls || urls.length < 1) &&
			(!optimisticUrls || optimisticUrls.length < 1)
		) {
			return (
				<div
					className={
						classNames.emptyPreview ||
						`${classNamespace}emptyPreview`
					}
					style={styles.emptyPreview}
				/>
			);
		}
		let previews = [];
		const multiple = this.props.multiple;
		if (
			urls &&
			urls.length > 0 &&
			!(multiple === false && optimisticUrls && optimisticUrls.length > 0)
		) {
			previews = urls.map((url, key) => {
				if (url) {
					let imgPreviewStyle = {
						backgroundImage: `url(${url})`,
						borderColor: disabled
							? disabledBorderColor
							: borderColor,
					};

					if (this.props.size) {
						imgPreviewStyle = {
							...imgPreviewStyle,
							...{
								width: this.props.size,
								height: this.props.size,
							},
							...(styles.imagePreview || {}),
						};
					}

					const deletePreviewStyle = {
						...{
							color: disabled ? disabledColor : color,
							borderColor: disabled
								? disabledBorderColor
								: borderColor,
						},
						...(styles.deletePreview || {}),
					};

					return (
						<div
							className={
								classNames.imgPreview ||
								`${classNamespace}imgPreview`
							}
							key={key}
							style={imgPreviewStyle}
							onClick={(e) => {
								e.preventDefault();
								this.clickImage(key, url);
							}}
						>
							{!inButton ? (
								<div
									className={
										classNames.deletePreview ||
										`${classNamespace}deletePreview`
									}
									style={deletePreviewStyle}
									onClick={(e) => {
										e.preventDefault();
										e.stopPropagation();
										this.deleteImage(key, url);
									}}
								>
									{deleteElement || (
										<svg
											xmlns="http://www.w3.org/2000/svg"
											width="7.969"
											height="8"
											viewBox="0 0 7.969 8"
										>
											<path
												id="X_Icon"
												data-name="X Icon"
												style={{
													fill: disabled
														? disabledColor
														: color,
													fillRule: "evenodd",
												}}
												/* eslint-disable max-len */
												d="M562.036,606l2.849-2.863a0.247,0.247,0,0,0,0-.352l-0.7-.706a0.246,0.246,0,0,0-.352,0l-2.849,2.862-2.849-2.862a0.247,0.247,0,0,0-.352,0l-0.7.706a0.249,0.249,0,0,0,0,.352L559.927,606l-2.849,2.862a0.25,0.25,0,0,0,0,.353l0.7,0.706a0.249,0.249,0,0,0,.352,0l2.849-2.862,2.849,2.862a0.249,0.249,0,0,0,.352,0l0.7-.706a0.25,0.25,0,0,0,0-.353Z"
												/* eslint-enable max-len */
												transform="translate(-557 -602)"
											/>
										</svg>
									)}
								</div>
							) : (
								<div
									className={
										classNames.notification ||
										`${classNamespace}notification`
									}
									style={
										styles.notification
											? {
													...styles.notification,
													...{
														display: this.state
															.displayNotification
															? "block"
															: "none",
														backgroundColor: notificationBgColor,
														color: notificationColor,
													},
											  }
											: {
													display: this.state
														.displayNotification
														? "block"
														: "none",
													backgroundColor: notificationBgColor,
													color: notificationColor,
											  }
									}
								>
									<span>
										{this.props.notification ||
											this.buildPlus(
												disabled,
												notificationColor,
												disabledColor,
												plusElement
											)}
									</span>
								</div>
							)}
						</div>
					);
				}
				return null;
			});
		}
		if (optimisticUrls && optimisticUrls.length > 0) {
			const length = previews.length;
			previews = previews.concat(
				optimisticUrls.map((url, key) => {
					if (url) {
						let imgPreviewStyle = {
							backgroundImage: `url(${url})`,
							borderColor: disabled
								? disabledBorderColor
								: borderColor,
						};

						if (this.props.size) {
							imgPreviewStyle = {
								...imgPreviewStyle,
								...{
									width: this.props.size,
									height: this.props.size,
								},
								...(styles.imgPreview || {}),
							};
						}

						return (
							<div
								className={
									classNames.imgPreview ||
									`${classNamespace}imgPreview`
								}
								key={length + key}
								style={imgPreviewStyle}
							/>
						);
					}
					return null;
				})
			);
		}
		return previews;
	}

	@autobind
	async loadImages(files: FileList, url: string, onLoadEnd?: Function): any {
		if (url) {
			try {
				const imageFormData = new FormData();

				for (let i = 0; i < files.length; i++) {
					imageFormData.append(
						this.props.dataName,
						files[i],
						files[i].name
					);
				}

				let response = await fetch(url, {
					method: "POST",
					credentials: "include",
					body: imageFormData,
					headers: this.props.headers,
				});

				if (response && response.status && response.status === 200) {
					response = await response.json();
					const multiple = this.props.multiple;
					if (
						response instanceof Array ||
						typeof response === "string"
					) {
						let imagePreviewUrls = [];
						if (multiple === false) {
							imagePreviewUrls =
								response instanceof Array
									? response
									: [response];
						} else {
							imagePreviewUrls = this.state.imagePreviewUrls.concat(
								response
							);
						}
						this.setState({
							imagePreviewUrls,
							optimisticPreviews: [],
							loadState: "success",
						});
						if (onLoadEnd && typeof onLoadEnd === "function") {
							onLoadEnd(false, response);
						}
					} else {
						const err = {
							message: "invalid response type",
							response,
							fileName: "ImagesUploader",
						};
						this.setState({
							loadState: "error",
							optimisticPreviews: [],
						});
						if (onLoadEnd && typeof onLoadEnd === "function") {
							onLoadEnd(err);
						}
					}
				} else {
					const err = {
						message: "server error",
						status: response ? response.status : false,
						response,
						fileName: "ImagesUploader",
					};
					this.setState({
						loadState: "error",
						optimisticPreviews: [],
					});
					if (onLoadEnd && typeof onLoadEnd === "function") {
						onLoadEnd(err);
					}
				}
			} catch (err) {
				if (onLoadEnd && typeof onLoadEnd === "function") {
					onLoadEnd(err);
				}
				this.setState({
					loadState: "error",
					optimisticPreviews: [],
				});
			}
		}
	}

	@autobind
	handleImageChange(e: Object) {
		e.preventDefault();

		const filesList = e.target.files;
		// Return when cancel button click but onChange event trigger
		if (filesList.length === 0) {
			return;
		}

		const {
			onLoadStart,
			onLoadEnd,
			url,
			optimisticPreviews,
			multiple,
		} = this.props;

		if (onLoadStart && typeof onLoadStart === "function") {
			onLoadStart();
		}

		this.setState({
			loadState: "loading",
		});
		if (
			this.props.max &&
			filesList.length + this.state.imagePreviewUrls.length >
				this.props.max
		) {
			const err = {
				message: "exceeded the number",
			};
			this.setState({
				loadState: "error",
				optimisticPreviews: [],
			});
			if (onLoadEnd && typeof onLoadEnd === "function") {
				onLoadEnd(err);
			}
			return;
		}

		for (let i = 0; i < filesList.length; i++) {
			const file = filesList[i];

			if (optimisticPreviews) {
				const reader = new FileReader();
				reader.onload = (upload) => {
					if (multiple === false) {
						this.setState({
							optimisticPreviews: [upload.target.result],
						});
					} else {
						const prevOptimisticPreviews = this.state
							.optimisticPreviews;
						this.setState({
							optimisticPreviews: prevOptimisticPreviews.concat(
								upload.target.result
							),
						});
					}
				};
				reader.readAsDataURL(file);
			}

			if (!file.type.match("image.*")) {
				const err = {
					message: "file type error",
					type: file.type,
					fileName: "ImagesUploader",
				};
				if (onLoadEnd && typeof onLoadEnd === "function") {
					onLoadEnd(err);
				}
				this.setState({
					loadState: "error",
				});
				return;
			}
		}

		if (url) {
			this.loadImages(filesList, url, onLoadEnd);
		}
	}

	@autobind
	handleFileDrop(files: FileList) {
		if (!this.props.disabled) {
			this.handleImageChange({
				preventDefault: () => true,
				target: {
					files,
				},
			});
		}
	}

	/* eslint-disable max-len, no-undef */
	buildPlus(
		disabled: boolean,
		color: string,
		disabledColor: string,
		plusElement?: string | React$Element<*>
	) {
		return (
			plusElement || (
				<svg
					version="1.1"
					xmlns="http://www.w3.org/2000/svg"
					style={{
						width: 35,
						fill: disabled ? disabledColor : color,
					}}
					xmlnsXlink="http://www.w3.org/1999/xlink"
					x="0px"
					y="0px"
					viewBox="0 0 1000 1000"
					enableBackground="new 0 0 1000 1000"
					xmlSpace="preserve"
				>
					<g>
						<path d="M500,10c13.5,0,25.1,4.8,34.7,14.4C544.2,33.9,549,45.5,549,59v392h392c13.5,0,25.1,4.8,34.7,14.4c9.6,9.6,14.4,21.1,14.4,34.7c0,13.5-4.8,25.1-14.4,34.6c-9.6,9.6-21.1,14.4-34.7,14.4H549v392c0,13.5-4.8,25.1-14.4,34.7c-9.6,9.6-21.1,14.4-34.7,14.4c-13.5,0-25.1-4.8-34.7-14.4c-9.6-9.6-14.4-21.1-14.4-34.7V549H59c-13.5,0-25.1-4.8-34.7-14.4C14.8,525.1,10,513.5,10,500c0-13.5,4.8-25.1,14.4-34.7C33.9,455.8,45.5,451,59,451h392V59c0-13.5,4.8-25.1,14.4-34.7C474.9,14.8,486.5,10,500,10L500,10z" />
					</g>
				</svg>
			)
		);
	}
	/* eslint-enable max-len, no-undef */

	@autobind
	buildButtonContent() {
		const {
			multiple,
			classNamespace,
			disabled,
			classNames,
			styles,
			color,
			disabledColor,
			plusElement,
		} = this.props;

		const pseudobuttonContentStyle = {
			...{
				color: disabled ? disabledColor : color,
			},
			...styles.pseudobuttonContent,
		};

		if (multiple !== false) {
			return (
				<span
					className={
						classNames.pseudobuttonContent ||
						`${classNamespace}pseudobuttonContent`
					}
					style={pseudobuttonContentStyle}
				>
					{this.buildPlus(
						disabled,
						color,
						disabledColor,
						plusElement
					)}
				</span>
			);
		}
		const { imagePreviewUrls, optimisticPreviews } = this.state;
		if (
			(!imagePreviewUrls || imagePreviewUrls.length < 1) &&
			(!optimisticPreviews || optimisticPreviews.length < 1)
		) {
			return (
				<span
					className={
						classNames.pseudobuttonContent ||
						`${classNamespace}pseudobuttonContent`
					}
					style={pseudobuttonContentStyle}
				>
					{this.buildPlus(
						disabled,
						color,
						disabledColor,
						plusElement
					)}
				</span>
			);
		}
		return this.buildPreviews(imagePreviewUrls, optimisticPreviews, true);
	}

	@autobind
	buildClose() {
		const {
			multiple,
			classNamespace,
			disabled,
			classNames,
			styles,
			color,
			disabledColor,
			borderColor,
			disabledBorderColor,
			deleteElement,
		} = this.props;

		if (multiple !== false) {
			return null;
		}
		const { imagePreviewUrls } = this.state;
		if (!imagePreviewUrls || imagePreviewUrls.length < 1) {
			return null;
		}

		const deletePreviewStyle = {
			...{
				color: disabled ? disabledColor : color,
				borderColor: disabled ? disabledBorderColor : borderColor,
			},
			...(styles.deletePreview || {}),
		};

		return (
			<div
				className={
					classNames.deletePreview || `${classNamespace}deletePreview`
				}
				style={deletePreviewStyle}
				onClick={(e) => {
					e.preventDefault();
					this.deleteImage(0);
				}}
			>
				{deleteElement || (
					<svg
						xmlns="http://www.w3.org/2000/svg"
						width="7.969"
						height="8"
						viewBox="0 0 7.969 8"
					>
						<path
							id="X_Icon"
							data-name="X Icon"
							style={{
								fill: disabled ? disabledColor : color,
								fillRrule: "evenodd",
							}}
							/* eslint-disable max-len */
							d="M562.036,606l2.849-2.863a0.247,0.247,0,0,0,0-.352l-0.7-.706a0.246,0.246,0,0,0-.352,0l-2.849,2.862-2.849-2.862a0.247,0.247,0,0,0-.352,0l-0.7.706a0.249,0.249,0,0,0,0,.352L559.927,606l-2.849,2.862a0.25,0.25,0,0,0,0,.353l0.7,0.706a0.249,0.249,0,0,0,.352,0l2.849-2.862,2.849,2.862a0.249,0.249,0,0,0,.352,0l0.7-.706a0.25,0.25,0,0,0,0-.353Z"
							/* eslint-enable max-len */
							transform="translate(-557 -602)"
						/>
					</svg>
				)}
			</div>
		);
	}

	@autobind
	showNotification() {
		const { multiple, disabled } = this.props;
		const { imagePreviewUrls } = this.state;
		if (
			!disabled &&
			multiple === false &&
			imagePreviewUrls &&
			imagePreviewUrls.length > 0
		) {
			this.setState({
				displayNotification: true,
			});
		}
	}

	@autobind
	hideNotification() {
		const { multiple } = this.props;
		if (multiple === false) {
			this.setState({
				displayNotification: false,
			});
		}
	}

	render() {
		const { imagePreviewUrls, loadState, optimisticPreviews } = this.state;
		const {
			inputId,
			disabled,
			multiple,
			label,
			size,
			classNamespace,
			classNames,
			styles,
			color,
			disabledColor,
			borderColor,
			disabledBorderColor,
		} = this.props;

		const containerClassNames = classnames({
			[classNames.container || `${classNamespace}container`]: true,
			disabled,
		});

		const loadContainerStyle = {
			...(size
				? {
						width: size,
						height: size,
				  }
				: {}),
			...{
				color: disabled ? disabledColor : color,
			},
			...(styles.loadContainer || {}),
		};

		const pseudobuttonStyle = {
			...(size
				? {
						width: size,
						height: size,
				  }
				: {}),
			...{
				color: disabled ? disabledColor : color,
			},
			...(styles.pseudobuttonStyle || {}),
		};

		const labelStyle = {
			...{
				color: disabled ? disabledColor : color,
			},
			...(styles.label || {}),
		};

		const dropzoneStyle = {
			...{
				borderColor: disabled ? disabledBorderColor : borderColor,
			},
			...(styles.dropzone || {}),
		};

		return (
			<div className={containerClassNames} style={styles.container || {}}>
				<label
					className={classNames.label || `${classNamespace}label`}
					style={labelStyle}
					htmlFor={inputId || "filesInput"}
				>
					{label || null}
				</label>
				<div
					className={
						classNames.filesInputContainer ||
						`${classNamespace}filesInputContainer`
					}
					style={styles.filesInputContainer}
				>
					<div
						className={
							classNames.loadContainer ||
							`${classNamespace}loadContainer`
						}
						style={loadContainerStyle}
					>
						{this.buildClose()}
						<Dropzone
							onDrop={this.handleFileDrop}
							disableClick
							accept="image/*"
							className={
								classNames.dropzone ||
								`${classNamespace}dropzone`
							}
							style={dropzoneStyle}
							multiple={
								/* eslint-disable no-unneeded-ternary */
								multiple === false ? false : true
								/* eslint-enable no-unneeded-ternary */
							}
						>
							<Button
								classNamespace={`${classNamespace}button-`}
								className={
									classNames.pseudobutton ||
									`${classNamespace}pseudobutton`
								}
								style={pseudobuttonStyle}
								onClick={(e) => {
									e.preventDefault();
									if (this.input) {
										this.input.click();
									}
								}}
							>
								{this.buildButtonContent()}
							</Button>
						</Dropzone>
					</div>
					<input
						name={inputId || "filesInput"}
						id={inputId || "filesInput"}
						className={
							classNames.fileInput || `${classNamespace}fileInput`
						}
						style={{
							...{
								display: "none",
							},
							...(styles.fileInput || {}),
						}}
						ref={(ref) => {
							this.input = ref;
						}}
						type="file"
						accept="image/*"
						multiple={multiple === false ? false : "multiple"}
						disabled={disabled || loadState === "loading"}
						onChange={this.handleImageChange}
					/>
				</div>
				{multiple !== false
					? this.buildPreviews(
							imagePreviewUrls,
							this.props.optimisticPreviews && optimisticPreviews
					  )
					: null}
			</div>
		);
	}
}

export default ImagesUploader;
