import * as React from 'react';
import Srl from "../data/Srl";
import * as Styles from "./SrlVisualizerStyles";
import SrlLayout from "./SrlLayout/SrlLayout";
import SrlAbsolute from "./SrlAbsolutePosition/SrlAbsolute";
import TokenAbsolute from "./SrlAbsolutePosition/TokenAbsolute";
import LabelAbsolute from "./SrlAbsolutePosition/LabelAbsolute";
import BarAbsolute from "./SrlAbsolutePosition/BarAbsolute";
import Frame from "../data/Frame";
import ArrowAbsolute from "./SrlAbsolutePosition/ArrowAbsolute";
import DefaultInks from "./Inks";
import TokensSelection from "./Selection/TokensSelection";
import RoleSelection from "./Selection/RoleSelection";
import DragingSelection from "./Selection/DragingSelection";
import {
    LINE_HEIGHT,
    LABEL_FONT_SIZE,
    TOKEN_FONT_SIZE,
    TRIANGLE_SIZE,
    SELECTION_BG,
    TOKEN_FONT_FAMILY, LABEL_FONT_FAMILY
} from './LayoutConf';
import measureHelper from "./measureHelper";
import {isRoleSelected, isTokenSelected} from "./Selection/isSelected";
import grey from "@material-ui/core/colors/grey";

export type Selection = null|TokensSelection|RoleSelection|DragingSelection;

function getTokenAndHostRect(width:number, tokens:string[]) {
    const host = document.createElement('div');
    host.style.width = width+'px';
    host.style.fontSize = TOKEN_FONT_SIZE + 'px';
    host.style.fontFamily = TOKEN_FONT_FAMILY;
    const tokenNodes = tokens.map(t=>{
        const ret = document.createElement('span');
        ret.style.whiteSpace = "nowrap";
        ret.textContent = t;
        return ret;
    });
    for(let i=0; i<tokenNodes.length; i++) {
        const tokenNode = tokenNodes[i];
        if(i>0) host.appendChild(document.createTextNode(" "));
        host.appendChild(tokenNode);
    }
    measureHelper.appendChild(host);
    const ret = {
        hostRect: host.getBoundingClientRect(),
        tokenRects: tokenNodes.map(n=>n.getBoundingClientRect())
    };
    measureHelper.innerHTML = "";
    return ret;
}

function getRoleRect(frames:Frame[]):Map<string,ClientRect> {

    const names:string[] = [];
    for(const frame of frames) {
        for(const role of frame.roles) {
            names.push(role.name);
        }
    }
    const host = document.createElement('div');
    host.style.fontSize = LABEL_FONT_SIZE+'px';
    host.style.fontFamily = LABEL_FONT_FAMILY;
    const nodes = names.map(name=>{
        const ret = document.createElement('span');
        ret.style.whiteSpace = "nowrap";
        ret.textContent = name;
        return ret;
    });
    for(const node of nodes) {
        host.appendChild(node);
        host.appendChild(document.createTextNode(" "));
    }
    measureHelper.appendChild(host);
    const ret = new Map<string, ClientRect>();
    for(let i=0; i<names.length; i++) {
        const name = names[i];
        const node = nodes[i];
        ret.set(name, node.getBoundingClientRect());
    }
    measureHelper.innerHTML = "";
    return ret;
}

type Props = {
    srl: Srl,
    noArrow?: boolean,
    inks?: typeof DefaultInks,
    onRequestSelectionChange?: (selection:Selection)=>void,
    onLayoutChange?: (layout:SrlAbsolute)=>void,
    width?: number;
};

type State = {
    srlAbsolute: null|SrlAbsolute,
    width: null|number,
    hovered: Selection
};

class SrlVisualizerNoSelection extends React.PureComponent<Props,State> {
    selection: Selection = null;
    constructor(props:Props) {
        super(props);
        let state:State = {width:null, srlAbsolute:null, hovered:null};
        const width = this.props.width;
        if(width) {
            const {tokenRects, hostRect} = getTokenAndHostRect(width, props.srl.tokens);
            const roleRects = getRoleRect(props.srl.frames);
            const srlLayout = new SrlLayout(props.srl, tokenRects, roleRects,hostRect);
            const srlAbsolute = new SrlAbsolute(srlLayout, !props.noArrow, TOKEN_FONT_SIZE*LINE_HEIGHT, LABEL_FONT_SIZE*LINE_HEIGHT, width);
            state.srlAbsolute = srlAbsolute;
            this.handleLayoutChange(srlAbsolute);
        }
        this.state = state;
    }
    getWidth() {
        return SrlVisualizerNoSelection.getWidth(this.props, this.state);
    }
    static getWidth(props:Props, state:State) {
        if(typeof props.width !== 'undefined') return props.width;
        return state.width;
    }
    select(selection:Selection):void {
        this.selection = selection;
        if(this.props.onRequestSelectionChange) this.props.onRequestSelectionChange(selection);
    }
    handleLayoutChange(srlAbsolute:SrlAbsolute) {
        if(this.props.onLayoutChange) this.props.onLayoutChange(srlAbsolute);
    }
    componentWillReceiveProps(newProps:Props) {
        if(newProps.srl === this.props.srl && newProps.noArrow === this.props.noArrow) return;
        this.setState({width:null, srlAbsolute:null});
    }
    componentWillUpdate(nextProps:Props, nextState:State) {
        const width = SrlVisualizerNoSelection.getWidth(nextProps, nextState);
        if(nextState.srlAbsolute == null && width != null) {
            const {tokenRects, hostRect} = getTokenAndHostRect(width, nextProps.srl.tokens);
            const roleRects = getRoleRect(nextProps.srl.frames);
            const srlLayout = new SrlLayout(nextProps.srl, tokenRects, roleRects,hostRect);
            const srlAbsolute = new SrlAbsolute(srlLayout, !nextProps.noArrow, TOKEN_FONT_SIZE*LINE_HEIGHT, LABEL_FONT_SIZE*LINE_HEIGHT, width);
            this.setState({srlAbsolute});
            this.handleLayoutChange(srlAbsolute);
        }
    }
    handleRefToMeasureWidth = (node:HTMLDivElement) => {
        if(node == null) return;
        if(this.state.width != null) return;
        this.setState({width:node.offsetWidth});
    };
    render() {
        const width = this.getWidth();
        if(width == null) {
            return <div ref={this.handleRefToMeasureWidth} />
        }
        if(this.state.srlAbsolute) return this.renderSvg(this.state.srlAbsolute);
        return null;
    }

    renderSvg(srlAbsolute:SrlAbsolute) {

        return <svg width={srlAbsolute.width} height={srlAbsolute.height} style={{userSelect:'none',display:'block'}}
                    onMouseUp={this.onRootMouseUp}>
            {this.renderSvgTokens(srlAbsolute.tokens)}
            {this.renderSvgLabels(srlAbsolute.labels)}
            {this.renderSvgBars(srlAbsolute.bars)}
            {this.renderSvgArrowHeads(srlAbsolute.labels)}
            {this.renderSvgArrowTails(srlAbsolute.arrows)}
        </svg>
    }
    onWindowMouseUp = ()=>{
        if(this.selection instanceof DragingSelection) {
            this.select(TokensSelection.fromDragingSelection(this.selection));
        }
    };
    onRootMouseUp = (e:React.MouseEvent<SVGSVGElement>)=>{
        if(e.target instanceof Element && e.target.hasAttribute('data-haslistener')) return;
        this.select(null);
    };
    componentWillMount() {window.addEventListener('mouseup',this.onWindowMouseUp)} // event listeners for drag-selection
    componentWillUnmount() { window.removeEventListener('mouseup',this.onWindowMouseUp)}

    renderSvgTokens(tokenAbsolutes:TokenAbsolute[]) {
        const tokenStrs:string[] = this.props.srl.tokens;
        return tokenAbsolutes.map(token=>{
            const commonTextStyle = {
                x:token.x, y:token.y+token.h, fontSize:TOKEN_FONT_SIZE, fontFamily: TOKEN_FONT_FAMILY
            };
            const rectFill = isTokenSelected(this.state.hovered, token.index, this.props.srl.frames)?grey[300]:"none";

            // event listeners for drag-selection
            let evtListeners = {
                onMouseDown:()=>{this.select(new DragingSelection(token.index, token.index))},
                onMouseOver:()=>{
                    this.setState({hovered:new TokensSelection(token.index, token.index+1)});
                    if(this.selection instanceof DragingSelection) {
                        this.select(new DragingSelection(this.selection.anchor1, token.index))
                    }
                },
                onMouseOut:()=>{
                    this.setState({hovered:null});
                }
            };

            return <g key={token.index} {...evtListeners} data-haslistener>
                <rect data-haslistener x={token.x-2} y={token.y} width={token.w+4} height={TOKEN_FONT_SIZE*LINE_HEIGHT} fill={rectFill}/>
                <text data-haslistener fill="black" {...commonTextStyle}>{tokenStrs[token.index]}</text>
            </g>
        })
    }
    getFrameInk(frameIndex:number) {
        const inks = this.props.inks || DefaultInks;
        return inks[frameIndex % inks.length];
    }
    renderSvgLabels(labelAbsolutes:LabelAbsolute[]) {
        const frames:Frame[] = this.props.srl.frames;
        return labelAbsolutes.map(label=>{
            const key = `${label.frameIndex},${label.roleIndex},${label.labelIndex}`;
            const commonTextStyle = {x:label.x,y:label.y+label.h,fontSize:LABEL_FONT_SIZE,fontFamily:LABEL_FONT_FAMILY};
            const textContent = frames[label.frameIndex].roles[label.roleIndex].name;
            const rectFill = isRoleSelected(this.state.hovered, label.frameIndex, label.roleIndex)?grey[300]:"none";
            const eveListeners = {
                onMouseOver: () => {
                    this.setState({hovered: new RoleSelection(label.frameIndex, label.roleIndex)})
                },
                onMouseOut: () => {
                    this.setState({hovered: null})
                }
            };
            return <g key={`${label.frameIndex},${label.roleIndex},${label.labelIndex}`} {...eveListeners}>
                <rect data-haslistener x={label.x - 2} y={label.y} width={label.w+4} height={LABEL_FONT_SIZE*LINE_HEIGHT} fill={rectFill} />
                <text onClick={e=>this.select(new RoleSelection(label.frameIndex, label.roleIndex))}
                      data-haslistener
                      fill={this.getFrameInk(label.frameIndex)}
                      {...commonTextStyle}>{textContent}</text>
            </g>
        })
    }
    renderSvgBars(barAbsolutes:BarAbsolute[]) {
        return barAbsolutes.map(bar=><line
            key={`${bar.frameIndex},${bar.roleIndex},${bar.labelIndex}`}
            stroke={this.getFrameInk(bar.frameIndex)}
            x1={bar.x1} y1={bar.y} x2={bar.x2} y2={bar.y} />)
    }
    renderSvgArrowHeads(labelAbsolutes:LabelAbsolute[]) {
        const frames:Frame[] = this.props.srl.frames;
        return labelAbsolutes.map(label=>{
            if(!frames[label.frameIndex].roles[label.roleIndex].isPredicate) return null;
            const key = `${label.frameIndex},${label.roleIndex},${label.labelIndex}`;
            const x = label.x + label.w/2-TRIANGLE_SIZE/2;
            const y = label.y;
            return <path key={key} fill={this.getFrameInk(label.frameIndex)}
                         d={'M '+ x + " " + y + " l "+TRIANGLE_SIZE+" 0 l "+(-TRIANGLE_SIZE/2) + " "+TRIANGLE_SIZE+ " Z"}
            />
        })
    }
    renderSvgArrowTails(arrowAbsolutes:ArrowAbsolute[]) {
        return arrowAbsolutes.map((arrow,i)=>{
            const beginX = arrow.fromX;
            const y = arrow.bottomY + TRIANGLE_SIZE/2;
            const endX = arrow.toX;
            const top = y - arrow.height;
            return <path key={'R'+i}  stroke={this.getFrameInk(arrow.frameIndex)} fill="none"
                         d={'M '+beginX + " " + y + " Q "+beginX+" "+top+" " + (beginX + endX)/2 + " " + top + " T "+endX+" "+y}/>
        });
    }
}

export default SrlVisualizerNoSelection;