import React, { createRef } from "react";
import path from "path";
import { Link } from "react-router-dom";
import { DragSource, DropTarget } from "react-dnd";

import "./thing.scss";
import { Card, NgIf, Icon, EventEmitter, img_placeholder, Input } from "../../components/";
import { pathBuilder, basename, filetype, prompt, alert, leftPad, getMimeType, debounce, memory } from "../../helpers/";
import { Files } from "../../model/";
import { ShareComponent } from "./share";
import { TagComponent } from "./tag";
import { t } from "../../locales/";


const canDrop = (props, monitor) => {
    const file = monitor.getItem();
    if (props.file.type !== "directory") return false;
    else if (file.name === props.file.name) return false;
    else if (props.file.icon === "loading") return false;
    else if (props.selected === true) return false;
    return true;
};

const HOCDropTargetForFsFile = (Cmp) => {
    const nativeFileTarget = {
        canDrop,
        drop(props, monitor) {
            const path = pathBuilder(props.path, props.file.name, "directory");
            props.emit(
                "file.upload",
                path,
                monitor.getItem(),
            );
        },
    };

    return DropTarget(
        "__NATIVE_FILE__",
        nativeFileTarget,
        (connect, monitor) => ({
            connectDropNativeFile: connect.dropTarget(),
            nativeFileIsOver: monitor.isOver(),
            canDropNativeFile: monitor.canDrop(),
        }),
    )(Cmp);
};

const HOVDropTargetForVirtualFile = (Cmp) => {
    const fileTarget = {
        canDrop,
        drop(props, monitor, component) {
            const src = monitor.getItem();
            const dest = props.file;

            if (props.currentSelection.length === 0) {
                const from = pathBuilder(props.path, src.name, src.type);
                const to = pathBuilder(props.path, "./"+dest.name+"/"+src.name, src.type);
                return {
                    action: "rename",
                    args: [from, to, src.type],
                    ctx: "existingfile",
                };
            } else {
                return {
                    action: "rename.multiple",
                    args: props.currentSelection.map((selectionPath) => {
                        const from = selectionPath;
                        const to = pathBuilder(
                            props.path,
                            "./"+dest.name+"/"+basename(selectionPath),
                            filetype(selectionPath),
                        );
                        return [from, to];
                    }),
                };
            }
        },
    };
    return DropTarget(
        "file",
        fileTarget,
        (connect, monitor) => ({
            connectDropFile: connect.dropTarget(),
            fileIsOver: monitor.isOver(),
            canDropFile: monitor.canDrop(),
        }),
    )(Cmp);
};

const HOVDropSourceForVirtualFile = (Cmp) => {
    const fileSource = {
        beginDrag(props, monitor, component) {
            return {
                path: props.path,
                name: props.file.name,
                type: props.file.type,
            };
        },
        canDrag(props, monitor) {
            if (props.metadata.can_move === false) {
                return false;
            }
            if (props.file.icon === "loading") return false;
            else if (props.selected === false && props.currentSelection.length > 0) return false;
            return true;
        },
        endDrag(props, monitor, component) {
            if (monitor.didDrop() && component.state.icon !== "loading") {
                const result = monitor.getDropResult();
                if (result.action === "rename") {
                    props.emit.apply(component, ["file.rename"].concat(result.args));
                } else if (result.action === "rename.multiple") {
                    props.emit.call(component, "file.rename.multiple", result.args);
                } else {
                    throw new Error("unknown action");
                }
            }
        },
    };

    return DragSource(
        "file",
        fileSource,
        (connect, monitor) => ({
            connectDragSource: connect.dragSource(),
            isDragging: monitor.isDragging(),
        }),
    )(Cmp);
};

class ExistingThingComponent extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            hover: null,
            filename: props.file.name,
            is_renaming: false,
            preview: null,
        };
    }

    shouldComponentUpdate(nextProps, nextState) {
        if (nextState.hover !== this.state.hover ||
            nextState.is_renaming !== this.state.is_renaming ||
            nextProps.view !== this.props.view ||
            this.state.preview !== nextState.preview ||
            this.props.fileIsOver !== nextProps.fileIsOver ||
            this.props.canDropFile !== nextProps.canDropFile ||
            this.props.nativeFileIsOver !== nextProps.nativeFileIsOver ||
            this.props.canDropNativeFile !== nextProps.canDropNativeFile ||
            this.props.selected !== nextProps.selected) return true;
        return false;
    }

    componentDidMount() {
        this.updateThumbnail(this.props);
    }

    UNSAFE_componentWillReceiveProps(props) {
        if (props.view !== this.props.view) {
            this.updateThumbnail(props);
        }
    }

    updateThumbnail(props) {
        if (props.view === "grid" && props.icon !== "loading") {
            const mimetype = getMimeType(props.file.path);
            if (window.CONFIG.thumbnailer.indexOf(mimetype) !== -1) {
                Files.url(props.file.path).then((url) => {
                    this.setState({ preview: url+"&thumbnail=true" });
                });
            }
        }
    }

    onRename(newFilename) {
        if (typeof newFilename === "string") {
            this.props.emit(
                "file.rename",
                pathBuilder(this.props.path, this.props.file.name, this.props.file.type),
                pathBuilder(this.props.path, newFilename, this.props.file.type),
                this.props.file.type,
            );
        }
        this.setState({ is_renaming: false });
    }

    onRenameRequest(force) {
        let new_state = !this.state.is_renaming;
        if (typeof force === "boolean") {
            new_state = force;
        }
        this.setState({ is_renaming: new_state });
    }

    onDeleteRequest(filename) {
        prompt.now(
            t("Confirm by typing") +" \""+this._confirm_delete_text()+"\"",
            (answer) => { // click on ok
                if (answer === this._confirm_delete_text()) {
                    this.setState({ icon: "loading" });
                    this.props.emit(
                        "file.delete",
                        pathBuilder(this.props.path, this.props.file.name, this.props.file.type),
                        this.props.file.type,
                    );
                    return Promise.resolve();
                } else {
                    return Promise.reject(t("Doesn't match"));
                }
            },
            () => {/* click on cancel */},
        );
    }
    onDeleteConfirm(answer) {
        if (answer === this._confirm_delete_text()) {
            this.setState({ icon: "loading", delete_request: false });
            this.props.emit(
                "file.delete",
                pathBuilder(this.props.path, this.props.file.name, this.props.file.type),
                this.props.file.type,
            );
        } else {
            this.setState({ delete_error: t("Doesn't match") });
        }
    }
    onDeleteCancel() {
        this.setState({ delete_request: false });
    }

    onTagRequest() {
        alert.now(
            <TagComponent path={this.props.file.path} type={this.props.file.type} />,
            () => {},
        )
    }

    onShareRequest(filename) {
        alert.now(
            <ShareComponent path={this.props.file.path} type={this.props.file.type} />,
            (ok) => {},
        );
    }

    onThingClick(e) {
        if (e.ctrlKey === true || e.target.classList.contains("component_checkbox")) {
            e.preventDefault();
            this.props.emit(
                "file.select",
                pathBuilder(this.props.path, this.props.file.name, this.props.file.type),
            );
        }
    }

    onThingClickCheckbox(e) {
        e.preventDefault();
        this.props.emit(
            "file.select",
            pathBuilder(this.props.path, this.props.file.name, this.props.file.type),
        );
    }

    _confirm_delete_text() {
        return this.props.file.name.length > 16?
            this.props.file.name.substring(0, 10).toLowerCase() :
            this.props.file.name;
    }

    render(highlight) {
        const { connectDragSource, connectDropFile, connectDropNativeFile } = this.props;
        let className = "";
        if (this.props.isDragging) {
            className += "is-dragging ";
        }
        if ((this.props.fileIsOver && this.props.canDropFile) || (this.props.nativeFileIsOver && this.props.canDropNativeFile)) {
            className += "file-is-hover ";
        }
        if (this.state.is_renaming) {
            className += "highlight ";
        }
        if (this.props.file.icon === "loading") {
            className += "loading ";
        }
        if (this.state.preview) {
            className += "preview ";
        }
        className = className.trim();

        const fileLink = this.props.file.link
            .replace(/%2F/g, "/")
            .replace(/\%/g, "%2525") // Hack to get the Link Component to work
            .replace(/\?/g, "%3F")
            .replace(/\#/g, "%23");

        const onClickCheckbox = (e, scaleNumber) => {
            const $box = e.target.parentElement.parentElement.parentElement;
            if ($box.classList.contains("view-grid") === false) return;
            $box.style.transform = `scale(${scaleNumber})`;
        };

        return connectDragSource(connectDropNativeFile(connectDropFile(
            <div className={"component_thing" + ` view-${this.props.view}`+
                            (this.props.selected === true ? " selected" : " not-selected")}>
                <ToggleableLink
                    onClick={this.onThingClick.bind(this)}
                    to={fileLink + window.location.search}
                    disabled={this.props.file.icon === "loading" || this.state.is_renaming}>
                    <Card
                        className={className + " " + this.state.hover}>
                        <Input type="checkbox" checked={this.props.selected} onMouseUp={(e) => onClickCheckbox(e, 1)} onMouseDown={(e) => onClickCheckbox(e, 0.95)}/>
                        <Image
                            preview={this.state.preview}
                            icon={this.props.file.icon || this.props.file.type}
                            view={this.props.view}
                            path={path.join(this.props.path, this.props.file.name)}
                            hide_extension={this.props.metadata.hide_extension} />
                        <Filename
                            filename={this.props.file.name}
                            filesize={this.props.file.size}
                            filetype={this.props.file.type}
                            hide_extension={this.props.metadata.hide_extension}
                            onRename={this.onRename.bind(this)}
                            is_renaming={this.state.is_renaming}
                            onRenameCancel={this.onRenameRequest.bind(this, false)} />
                        <DateTime
                            show={this.state.icon !== "loading"}
                            timestamp={this.props.file.time} />
                        <ActionButton
                            onClickRename={this.onRenameRequest.bind(this)}
                            onClickDelete={this.onDeleteRequest.bind(this)}
                            onClickShare={this.onShareRequest.bind(this)}
                            onClickTag={this.onTagRequest.bind(this)}
                            is_renaming={this.state.is_renaming}
                            can_rename={this.props.metadata.can_rename !== false}
                            can_delete={this.props.metadata.can_delete !== false}
                            can_share={this.props.metadata.can_share !== false && window.CONFIG.enable_share === true} />
                        <div className="selectionOverlay"></div>
                    </Card>
                </ToggleableLink>
            </div>,
        )));
    }
}

export const ExistingThing = EventEmitter(
    HOCDropTargetForFsFile(
        HOVDropTargetForVirtualFile(
            HOVDropSourceForVirtualFile(
                ExistingThingComponent,
            ),
        ),
    ),
);

export default function ToggleableLink(props) {
    const { disabled, ...rest } = props;
    return disabled ?
        props.children :
        <Link {...rest}>{props.children}</Link>;
}

class Filename extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            filename: props.filename,
        };
    }

    onInputFocus(e) {
        let value = e.target.value.split(".");
        if (value.length > 1) {
            value.pop();
        }
        value = value.join(".");
        e.target.setSelectionRange(0, value.length);
    }

    onRename(e) {
        e.preventDefault();
        e.stopPropagation();
        this.props.onRename(this.state.filename);
        return false;
    }

    onCancel() {
        this.setState({ filename: this.props.filename });
        this.props.onRenameCancel();
    }

    preventSelect(e) {
        e.preventDefault();
        e.stopPropagation();
    }

    render() {
        const [fileWithoutExtension, fileExtension] = function(filename) {
            const fname = filename.split(".");
            if (fname.length < 2) {
                return [filename, ""];
            }
            const ext = fname.pop();
            if (window.CONFIG.mime[ext] === undefined) {
                return [filename, ""];
            }
            return [fname.join("."), "." + ext];
        }(this.state.filename);

        return (
            <span className="component_filename">
                <span className="file-details">
                    <NgIf cond={this.props.is_renaming === false} type="inline">
                        {
                            fileWithoutExtension
                        }{
                            this.props.hide_extension ? null :
                                <span className="extension">{fileExtension}</span>
                        }
                        <FileSize
                            type={this.props.filetype}
                            size={this.props.filesize} />
                    </NgIf>
                    <NgIf cond={this.props.is_renaming === true} type="inline">
                        <form
                            onClick={this.preventSelect}
                            onSubmit={(e) => { e.preventDefault(); e.stopPropagation(); this.onRename(e) }}>
                            <input
                                value={this.state.filename}
                                onChange={(e) => this.setState({ filename: e.target.value })}
                                onBlur={this.onCancel.bind(this)}
                                onFocus={this.onInputFocus.bind(this)}
                                autoFocus />
                        </form>
                    </NgIf>
                </span>
            </span>
        );
    }
}

const ActionButton = (props) => {
    const onRename = (e) => {
        e.preventDefault();
        props.onClickRename();
    };

    const onDelete = (e) => {
        e.preventDefault();
        props.onClickDelete();
    };

    const onShare = (e) => {
        e.preventDefault();
        props.onClickShare();
    };

    const onTag = (e) => {
        e.preventDefault();
        props.onClickTag();
    }

    return (
        <div className="component_action">
            <NgIf
                type="inline"
                cond={props.can_rename !== false && props.is_renaming === false}>
                <Icon
                    name="edit"
                    onClick={onRename}
                    className="component_updater--icon" />
            </NgIf>
            {
                /canary/.test(location.search) ? (
                    <span type="inline">
                        <Icon
                            name="tag"
                            onClick={onTag}
                            className="component_updater--icon" />
                    </span>
                ) : (
                    <NgIf
                        type="inline"
                        cond={props.can_delete !== false}>
                        <Icon
                            name="delete"
                            onClick={onDelete}
                            className="component_updater--icon" />
                    </NgIf>
                )
            }
            <NgIf
                type="inline"
                cond={props.can_share !== false}>
                <Icon
                    name="share"
                    onClick={onShare}
                    className="component_updater--icon" />
            </NgIf>
        </div>
    );
};

const DateTime = (props) => {
    function displayTime(timestamp) {
        if (!timestamp || timestamp < 0) {
            return "";
        }
        const t = new Date(timestamp);
        if("DateTimeFormat" in Intl) {
            const str = new Intl.DateTimeFormat({ dateStyle: "short" }).format(t);
            if (str.length <= 10) return str;
        }
        return t.getFullYear() + "-" + leftPad((t.getMonth() + 1).toString(), 2) + "-" + leftPad(t.getDate().toString(), 2);
    }

    if (props.show === false) {
        return null;
    }

    return (
        <span className="component_datetime">
            <span>{displayTime(props.timestamp)}</span>
        </span>
    );
};

const FileSize = (props) => {
    function displaySize(bytes) {
        if (Number.isNaN(bytes) || bytes < 0 || bytes === undefined) {
            return "";
        } else if (bytes < 1024) {
            return "("+bytes+"B)";
        } else if (bytes < 1048576) {
            return "("+Math.round(bytes/1024*10)/10+"KB)";
        } else if (bytes < 1073741824) {
            return "("+Math.round(bytes/(1024*1024)*10)/10+"MB)";
        } else if (bytes < 1099511627776) {
            return "("+Math.round(bytes/(1024*1024*1024)*10)/10+"GB)";
        } else {
            return "("+Math.round(bytes/(1024*1024*1024*1024))+"TB)";
        }
    }

    return (
        <NgIf type="inline" className="component_filesize" cond={props.type === "file"}>
            <span> {displaySize(props.size)}</span>
        </NgIf>
    );
};

class Image extends React.Component {
    constructor(props) {
        super(props);
    }

    render() {
        if (this.props.preview && this.props.view === "grid") {
            return (
                <span>
                    <div className="image_layer"></div>
                    <LazyLoadImage scroller=".scroll-y" className="thumbnail" src={this.props.preview} />
                </span>
            );
        }

        const ext = path.extname(this.props.path).replace(/^\./, "");
        return (
            <span>
                <Icon name={this.props.icon} />
                <NgIf
                    className="info_extension"
                    cond={!!ext && this.props.view === "grid" && this.props.icon === "file" && this.props.hide_extension !== true}>
                    <span>{ext}</span>
                </NgIf>
            </span>
        );
    }
};

class LazyLoadImage extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            appear: false,
            error: false,
        };
        this.$scroll = document.querySelector(props.scroller);
        this.onScroll = debounce(this.onScroll.bind(this), 250);
        this.$el = createRef();
    }

    componentDidMount() {
        if (!this.$scroll) throw new Error("No scroll detected on LazyLoadImage");
        this.$scroll.addEventListener("scroll", this.onScroll, { passive: true });
        this.onScroll();
    }
    componentWillUnmount() {
        this.$scroll.removeEventListener("scroll", this.onScroll);
    }

    onScroll() {
        if (!this.$el.current) return this.componentWillUnmount();
        const dim_el = this.$el.current.getBoundingClientRect();
        if (dim_el.top + dim_el.height > 0 && dim_el.top < window.innerHeight) {
            this.componentWillUnmount();
            memory.set(this.props.src, true);
            this.setState({ appear: true });
        }
    }

    onError() {
        this.setState({ error: true });
    }

    render() {
        if ((this.props.preview || memory.get(this.props.src) === null) || this.state.error === true) {
            return (
                <img
                    ref={this.$el}
                    className={this.props.className}
                    src={img_placeholder} />
            );
        }
        return (
            <img
                onError={this.onError.bind(this)}
                className={this.props.className}
                src={this.props.src} />
        );
    }
}
