/**
 * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
 *
 * @author David Sehnal <david.sehnal@gmail.com>
 * @author Alexander Rose <alexander.rose@weirdbyte.de>
 */

import * as React from 'react'
import { ParamDefinition as PD } from 'mol-util/param-definition';
import { camelCaseToWords } from 'mol-util/string';

export interface ParameterControlsProps<P extends PD.Params = PD.Params> {
    params: P,
    values: any,
    onChange: ParamOnChange,
    isEnabled?: boolean,
    onEnter?: () => void
}

export class ParameterControls<P extends PD.Params> extends React.PureComponent<ParameterControlsProps<P>, {}> {
    render() {
        const common = {
            onChange: this.props.onChange,
            isEnabled: this.props.isEnabled,
            onEnter: this.props.onEnter,
        }
        const params = this.props.params;
        const values = this.props.values;
        return <div style={{ width: '100%' }}>
            {Object.keys(params).map(key => {
                const param = params[key];
                if (param.type === 'value') return null;
                if (param.type === 'mapped') return <MappedControl param={param} key={key} {...common} name={key} value={values[key]} />
                if (param.type === 'group') return <GroupControl param={param} key={key} {...common} name={key} value={values[key]} />
                return <ParamWrapper control={controlFor(param)} param={param} key={key} {...common} name={key} value={values[key]} />
            })}
        </div>;
    }
}

function controlFor(param: PD.Any): ValueControl {
    switch (param.type) {
        case 'boolean': return BoolControl;
        case 'number': return NumberControl;
        case 'multi-select': return MultiSelectControl;
        case 'color': return ColorControl;
        case 'select': return SelectControl;
        case 'text': return TextControl;
        case 'interval': return IntervalControl;
        case 'converted': return ConvertedControl;
        case 'group': throw Error('Must be handled separately');
        case 'mapped': throw Error('Must be handled separately');
    }
    throw new Error('not supported');
}

type ParamWrapperProps = { name: string, value: any, param: PD.Base<any>, onChange: ParamOnChange, control: ValueControl, onEnter?: () => void, isEnabled?: boolean }
export type ParamOnChange = (params: { param: PD.Base<any>, name: string, value: any }) => void
type ValueControlProps<P extends PD.Base<any> = PD.Base<any>> = { value: any, param: P, isEnabled?: boolean, onChange: (v: any) => void, onEnter?: () => void }
type ValueControl = React.ComponentClass<ValueControlProps<any>>

function getLabel(name: string, param: PD.Base<any>) {
    return param.label === undefined ? camelCaseToWords(name) : param.label
}

export class ParamWrapper extends React.PureComponent<ParamWrapperProps> {
    onChange = (value: any) => {
        this.props.onChange({ param: this.props.param, name: this.props.name, value });
    }

    render() {
        return <div style={{ padding: '0 3px', borderBottom: '1px solid #ccc' }}>
            <div style={{ lineHeight: '20px', float: 'left' }} title={this.props.param.description}>{getLabel(this.props.name, this.props.param)}</div>
            <div style={{ float: 'left', marginLeft: '5px' }}>
                <this.props.control value={this.props.value} param={this.props.param} onChange={this.onChange} onEnter={this.props.onEnter} isEnabled={this.props.isEnabled} />
            </div>
            <div style={{ clear: 'both' }} />
        </div>;
    }
}

export class BoolControl extends React.PureComponent<ValueControlProps> {
    onClick = () => {
        this.props.onChange(!this.props.value);
    }

    render() {
        return <button onClick={this.onClick} disabled={!this.props.isEnabled}>{this.props.value ? '✓ On' : '✗ Off'}</button>;
    }
}

export class NumberControl extends React.PureComponent<ValueControlProps<PD.Numeric>, { value: string }> {
    // state = { value: this.props.value }
    onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
        this.props.onChange(+e.target.value);
        // this.setState({ value: e.target.value });
    }

    render() {
        return <input type='range'
            value={'' + this.props.value}
            min={this.props.param.min}
            max={this.props.param.max}
            step={this.props.param.step}
            onChange={this.onChange}
        />;
    }
}

export class TextControl extends React.PureComponent<ValueControlProps<PD.Text>> {
    onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
        const value = e.target.value;
        if (value !== this.props.value) {
            this.props.onChange(value);
        }
    }

    onKeyPress = (e: React.KeyboardEvent<HTMLInputElement>) => {
        if (!this.props.onEnter) return;
        if ((e.keyCode === 13 || e.charCode === 13)) {
            this.props.onEnter();
        }
    }

    render() {
        return <input type='text'
            value={this.props.value || ''}
            onChange={this.onChange}
            onKeyPress={this.props.onEnter ? this.onKeyPress : void 0}
        />;
    }
}

export class SelectControl extends React.PureComponent<ValueControlProps<PD.Select<any>>> {
    onChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
        this.setState({ value: e.target.value });
        this.props.onChange(e.target.value);
    }

    render() {
        return <select value={this.props.value || ''} onChange={this.onChange}>
            {this.props.param.options.map(([value, label]) => <option key={value} value={value}>{label}</option>)}
        </select>;
    }
}

export class MultiSelectControl extends React.PureComponent<ValueControlProps<PD.MultiSelect<any>>> {
    onChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
        const value = Array.from(e.target.options).filter(option => option.selected).map(option => option.value);
        this.setState({ value });
        this.props.onChange(value);
    }

    render() {
        return <select multiple value={this.props.value || ''} onChange={this.onChange}>
            {this.props.param.options.map(([value, label]) => <option key={label} value={value}>{label}</option>)}
        </select>;
    }
}

export class IntervalControl extends React.PureComponent<ValueControlProps<PD.Interval>> {
    // onChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
    //     this.setState({ value: e.target.value });
    //     this.props.onChange(e.target.value);
    // }

    render() {
        return <span>interval TODO</span>;
    }
}

export class ColorControl extends React.PureComponent<ValueControlProps<PD.Color>> {
    // onChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
    //     this.setState({ value: e.target.value });
    //     this.props.onChange(e.target.value);
    // }

    render() {
        return <span>color TODO</span>;
    }
}

export class ConvertedControl extends React.PureComponent<ValueControlProps<PD.Converted<any, any>>> {
    onChange = (v: any) => {
        console.log('onChange', v)
        this.props.onChange(this.props.param.toValue(v));
    }

    render() {
        const Control: ValueControl = controlFor(this.props.param.param as PD.Any);

        return <Control value={this.props.param.fromValue(this.props.value)} param={this.props.param} onChange={this.onChange} onEnter={this.props.onEnter} isEnabled={this.props.isEnabled} />
    }
}

type GroupWrapperProps = { name: string, value: PD.Group<any>['defaultValue'], param: PD.Group<any>, onChange: ParamOnChange, onEnter?: () => void, isEnabled?: boolean }
export class GroupControl extends React.PureComponent<GroupWrapperProps> {
    change(value: PD.Mapped<any>['defaultValue'] ) {
        this.props.onChange({ name: this.props.name, param: this.props.param, value });
    }

    onChangeParam: ParamOnChange = e => {
        const value: PD.Mapped<any>['defaultValue'] = this.props.value;
        this.change({ ...value.params, [e.name]: e.value });
    }

    render() {
        const value: PD.Mapped<any>['defaultValue'] = this.props.value;
        const params = this.props.param.params;

        return <div>
            <ParameterControls params={params} onChange={this.onChangeParam} values={value.params} onEnter={this.props.onEnter} isEnabled={this.props.isEnabled} />
        </div>
    }
}

type MappedWrapperProps = { name: string, value: PD.Mapped<any>['defaultValue'], param: PD.Mapped<any>, onChange: ParamOnChange, onEnter?: () => void, isEnabled?: boolean }
export class MappedControl extends React.PureComponent<MappedWrapperProps> {
    change(value: PD.Mapped<any>['defaultValue'] ) {
        this.props.onChange({ name: this.props.name, param: this.props.param, value });
    }

    onChangeName: ParamOnChange = e => {
        this.change({ name: e.value, params: this.props.param.map(e.value).defaultValue });
    }

    onChangeParam: ParamOnChange = e => {
        const value: PD.Mapped<any>['defaultValue'] = this.props.value;
        this.change({ name: value.name, params: e.value });
    }

    render() {
        const value: PD.Mapped<any>['defaultValue'] = this.props.value;
        const param = this.props.param.map(value.name);

        return <div>
            <ParamWrapper control={SelectControl} param={this.props.param.select}
                isEnabled={this.props.isEnabled} onChange={this.onChangeName} onEnter={this.props.onEnter}
                name={'name'} value={value.name} />
            <div style={{ borderLeft: '5px solid #777', paddingLeft: '5px' }}>
                {param.type === 'group'
                ? <GroupControl param={param} value={value} name='param' onChange={this.onChangeParam} onEnter={this.props.onEnter} isEnabled={this.props.isEnabled} />
                : param.type === 'mapped' || param.type === 'value' ? null
                : <ParamWrapper control={controlFor(param)} param={param} onChange={this.onChangeParam} onEnter={this.props.onEnter} isEnabled={this.props.isEnabled} name={'value'} value={value} />}
            </div>
        </div>
    }
}