import * as React from 'react';
import {FormattedMessage} from 'react-intl';
import {SubmissionError} from 'redux-form';
import {EqualsFieldValidatorRule} from './validator-rule/equals-field-validator-rule';
import {RequiredValidatorRule} from './validator-rule/required-validator-rule';
import {EmailValidatorRule} from './validator-rule/email-validator-rule';
import {OneOfValidatorRule} from './validator-rule/one-of-validator-rule';
import {AbstractValidatorRule} from './validator-rule/abstract-validator-rule';
import {CustomRule} from './validator-rule/custom-rule';
import {NumberValidatorRule, NumberValidatorRuleOptions} from './validator-rule/number-validator-rule';

export function formValuesTransformer(handler: (formProps: any) => Promise<any>, transformer: (input: {[key: string]: any}) => {[key: string]: any}) {
    return (values: any) => {
        values = transformer(values);
        return handler(values);
    };
}

export function submitFormError(data: { [key: string]: string }) {
    throw new SubmissionError(data);
}

export function submitFormErrorHandler(handler: (formProps: any) => Promise<any>) {
    return (values: any) => {
        return handler(values).catch(error => {
            if (error.response && error.response.data && error.response.data.fields) {
                submitFormError(error.response.data.fields);
            }
        });
    };
}

export default class FormValidator {

    private readonly values: any;
    private readonly props: any;
    private validators: Array<Validator> = [];

    public static make(values, props, ...validators: Array<Validator>) {
        return new FormValidator(values, props, ...validators);
    }

    public constructor(values, props, ...validators: Array<Validator>) {
        this.values = values;
        this.props = props;
        this.validators = validators;
    }

    public validate(): { [key: string]: React.ReactElement<any> } {
        let errors = {};
        this.validators.forEach(validator => {
            let error = validator.validate(this.values[validator.name], this.values, this.props);
            if (error) {
                if (!errors[validator.name]) {
                    errors[validator.name] = [];
                }
                errors[validator.name].push(
                    <FormattedMessage
                        id={error.message.id}
                        defaultMessage={error.message.defaultMessage}
                        values={error.values}
                    />
                );
            }
        });
        return errors;
    }

    public asyncValidate(): Promise<Array<ErrorMessage>> {
        let promises: Array<Promise<ErrorMessage>> = [];
        this.validators.forEach(validator => {
            promises.push(validator.asyncValidate(this.values[validator.name], this.values, this.props));
        });
        return Promise.all(promises);
    }

    /* QUICK RULES */

    public custom(name: string, callback: (value, name, values, props) => boolean) {
        this.validators.push(new Validator(name, new CustomRule(callback)));
        return this;
    }

    public email(name: string): FormValidator {
        this.validators.push(new Validator(name, new EmailValidatorRule()));
        return this;
    }

    public equalsField(name: string, other: string): FormValidator {
        this.validators.push(new Validator(name, new EqualsFieldValidatorRule(other)));
        return this;
    }

    public number(name: string, options?: NumberValidatorRuleOptions) {
        this.validators.push(new Validator(name, new NumberValidatorRule(options)));
        return this;
    }

    public oneOf(name: string, pool: Array<any>): FormValidator {
        this.validators.push(new Validator(name, new OneOfValidatorRule(pool)));
        return this;
    }

    public required(name: string): FormValidator {
        this.validators.push(new Validator(name, new RequiredValidatorRule()));
        return this;
    }

}

export class Validator {

    private _name: string;
    private _validatorRule: AbstractValidatorRule;

    public constructor(name: string, validatorRule: AbstractValidatorRule) {
        this._name = name;
        this._validatorRule = validatorRule;
    }

    get name(): string {
        return this._name;
    }

    set name(value: string) {
        this._name = value;
    }

    get validatorRule(): AbstractValidatorRule {
        return this._validatorRule;
    }

    set validatorRule(value: AbstractValidatorRule) {
        this._validatorRule = value;
    }

    private getMessageValues() {
        return {...{}, ...this.validatorRule.getMessageValues()};
    }

    public validate(value, values, props): ErrorMessage {
        return this.validatorRule.isValid(value, this.name, values, props) ? null :
            new ErrorMessage(this.validatorRule.message, this.getMessageValues());
    }

    public asyncValidate(value, values, props): Promise<ErrorMessage> {
        return new Promise<ErrorMessage>(((resolve, reject) => {
            this.validatorRule.isValid(value, this.name, values, props) ? resolve(null) :
                reject(new ErrorMessage(this.validatorRule.message, this.validatorRule.getMessageValues()));
        }));
    }

}

export class ErrorMessage {

    public message: FormattedMessage.MessageDescriptor;
    public values;

    constructor(message: ReactIntl.FormattedMessage.MessageDescriptor, values) {
        this.message = message;
        this.values = values;
    }
}
