<!DOCTYPE html>
<html>

<head>
    <link data-require="bootstrap-css@*" data-semver="3.3.6" rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.css" />
    <link rel="stylesheet" href="style.css" />
</head>

<body>
    <div id="app"></div>

    <script data-require="react@*" data-semver="0.14.2" src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.2/react.min.js"></script>
    <script data-require="react-with-addons@0.14.0" data-semver="0.14.0" src="http://fb.me/react-with-addons-0.14.0.js"></script>
    <script data-require="react-dom@*" data-semver="0.14.0" src="http://fb.me/react-dom-0.14.0.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.23/browser.min.js"></script>
    <script type="text/babel" src="script.js"></script>
</body>

</html>
/* Constants */

const FIELD_TYPE = {
    text: 'text',
    email: 'email',
    password: 'password',
    radiobutton: 'radiobutton'
};

const getSizeTypes = function() {
    let sizeTypes = [];
    const prefixes = ['xs-', 'sm-', 'md-', 'lg-', 'xs-offset-', 'sm-offset-', 'md-offset-', 'lg-offset-'];
    
    for (var i = 0; i < prefixes.length; i++) {
        for (var k = 0; k < 12; k++) {
            sizeTypes.push(prefixes[i] + (k + 1));
        }
    }
    
    return sizeTypes;
}

const validateSizeTypes = function(props, propName, componentName) {
    var values = props[propName].split(' ');
    var sizeTypes = getSizeTypes();
    
    for (var i = 0; i < values.length; i++) {
        if (!sizeTypes.includes(values[i])) {
            return new Error('Invalid prop `' + propName + '` supplied to' +
                ' `' + componentName + '`. Validation failed. The value must be one or more of types: ' +
                sizeTypes.join(', '));
        }
    }
}

/* Bootstrap elements wrapped in React components */

class Container extends React.Component {
    render() {
        return <div className="container">{this.props.children}</div>;
    }
}

class Row extends React.Component {
    render() {
        return <div className="row">{this.props.children}</div>
    }
}

// need fix: can exists another classNames (see FormGroup component)
class Col extends React.Component {
    render() {
        return <div className={this.renderClassName()}>{this.props.children}</div>;
    }
    
    renderClassName() {
        if (!this.props.size) {
            return 'col-sm-12';
        }
        
        const col = ' col-';
        const className = this.props.size.split(' ').map(clazz => col + clazz).join(' ');
        
        return className;
    }
}

Col.propTypes = {
    size: validateSizeTypes
};

class Form extends React.Component {
    render() {
        const className = (this.props.horizontal) ? 'form-horizontal' : '';
        
        return <form className={className}>{this.props.children}</form>;
    }
}

Form.propTypes = {
    horizontal: React.PropTypes.bool
};

class FormGroup extends React.Component {
    render() {
        return <div {...this.props} className={this.getCssClasses()}>{this.props.children}</div>;
    }
    
    getCssClasses() {
        let classNames = [];
        
        if (this.props.hasError) {
            classNames.push('has-error');
        }
        
        if (this.props.className) {
            classNames = [...classNames, ...this.props.className.split(' ')];
        }

        return [...classNames, 'form-group'].join(' ');
    }
};

FormGroup.propTypes = {
    hasError: React.PropTypes.bool
};

const Button = (props) => {
    let classNames = ['btn'];
    classNames = classNames.join(' ');
    
    return <button type="submit" className={classNames}>{props.children}</button>
};

class Validation extends React.Component {
    
    constructor(props) {
        super(props);
        
        this.validationRules = [
            { required: (rule) => (typeof rule.value === 'undefined' || rule.value === '') },
            { minLength: (rule) => rule.value.length < rule.minLength }
        ];
    }
    
    render() {
        return <span className="help-block text-danger">{this.getMessage()}</span>;
    }
    
    getMessage() {
        let message = '';
        
        this.validationRules.forEach((rule, index) => {
            if (!this.isRulePresent(rule)) {
                return;
            }
            
            let messageIsEmpty = !message;
            
            if (messageIsEmpty && this.isInvalid(this.props.value, rule)) {
                message = this.getMessageForRule(rule);
            }
        });
        
        return message;
    }
    
    isRulePresent(rule) {
        const ruleKey = Object.keys(rule)[0];
        
        const isRulePresentInProps = this.props.rules.some(rule => {
            const key = Object.keys(rule)[0];
            return key === ruleKey;
        });
        
        return isRulePresentInProps;
    }
    
    isInvalid(value, rule) {
        const ruleKey = Object.keys(rule)[0];
        const ruleFunction = rule[ruleKey];
        const propObject = this.props.rules.find(rule => Object.keys(rule)[0] === ruleKey)[ruleKey];
        
        console.log({ ...propObject, value: value });
        
        return ruleFunction({ ...propObject, value: value });
    }
    
    getMessageForRule(rule) {
        let message = '';
        const ruleKey = Object.keys(rule)[0];
        
        this.props.rules.forEach((rule) => {
            const key = Object.keys(rule)[0];
            
            if (ruleKey === key) {
                message = rule[key].message;
            }
        });
        
        return message;
    }
}

Validation.propTypes = {
    value: React.PropTypes.string.isRequired,
    rules: React.PropTypes.array.isRequired
};

/*
class FormField extends React.Component {
    render() {
        return (
            <FormGroup>
                {this.renderLabel()}
                {this.renderInputContainer()}
            </FormGroup>
        );
    }
    
    renderLabel() {
        if (!this.props.label) {
            return;
        }
        
        return <label className={this.getLabelCssClass()} htmlFor={this.props.name}>{this.props.label}</label>;
    }
    
    getLabelCssClass() {
        let cssClasses = ['control-label'];
        
        if (this.props.horizontal) {
            cssClasses.push('col-sm-2');
        }
        
        return cssClasses.join(' ');
    }
    
    renderInputContainer() {
        if (!this.props.horizontal) {
            return this.renderInput();
        }
        
        return (
            <div className="col-sm-10">
                {this.renderInput()}
            </div>
        );
    }
    
    renderInput() {
        const type = this.props.type || 'text';
        
        return (
            <input 
                className="form-control" 
                id={this.props.name} 
                type={type} 
                placeholder={this.props.placeholder} 
            />
        );
    }
}

FormField.propTypes = {
    type: React.PropTypes.oneOf(Object.keys(FIELD_TYPE)),
    horizontal: React.PropTypes.bool,
    name: React.PropTypes.string.isRequired,
    label: React.PropTypes.string,
    placeholder: React.PropTypes.string
};
*/

/* React components related to the App */

class PageHeader extends React.Component {
    render() {
        return (
            <Row>
                <Col size="sm-6 md-12">
                    <div className="page-header">
                        <h1>React Form</h1>
                    </div>
                </Col>
            </Row>
        );
    }
}

class AppForm extends React.Component {
    render() {
        return (
            <Form horizontal>
                <FormGroup hasError>
                    <label htmlFor="inputEmail3" className="col-sm-2 control-label">Email</label>
                    <Col size="sm-10">
                        <input 
                            className="form-control" 
                            id="inputEmail3" 
                            type="email" 
                            placeholder="Email" 
                            onInput={(e) => this.onInput('email', e.target.value )}
                        />
                        <Validation 
                            value={this.props.model.email}
                            rules={[
                                { required: { message: 'Email is required' } },
                                { minLength: { minLength: 5, message: 'At least 5 characters' } }
                            ]} 
                        />
                    </Col>
                </FormGroup>
                <FormGroup>
                    <label htmlFor="inputPassword3" className="col-sm-2 control-label">Password</label>
                    <Col size="sm-10">
                        <input 
                            className="form-control" 
                            id="inputPassword3" 
                            type="password" 
                            placeholder="Password" 
                            onInput={(e) => this.onInput('password', e.target.value )}
                        />
                    </Col>
                </FormGroup>
                <FormGroup>
                    <Col size="sm-offset-2 sm-10">
                        <div className="checkbox">
                            <label>
                                <input 
                                    type="checkbox" 
                                    onChange={(e) => {  this.onInput('rememberMe', e.target.checked ); }}
                                /> Remember me
                            </label>
                        </div>
                    </Col>
                </FormGroup>
                <FormGroup>
                    <Col size="sm-offset-2 sm-10">
                        <Button>Sign in</Button>
                    </Col>
                </FormGroup>
            </Form>
        );
    }
    
    onInput(key, value) {
        this.props.model[key] = value;
        const modelUpdated = this.props.model;
        this.props.onModelChange({ ...modelUpdated });
    }
}

AppForm.propTypes = {
    model: React.PropTypes.object.isRequired,
    onModelChange: React.PropTypes.func.isRequired
};

class App extends React.Component {
    constructor() {
        super();
        
        this.state = {
            model: {
                email: '',
                password: '',
                rememberMe: false
            }
        };
    }
    
    render() {
        return (
            <Container>
                <PageHeader />
                <Row>
                    <Col size="md-12">
                        <AppForm model={this.state.model} onModelChange={this.onChangeFormData.bind(this)} />
                    </Col>
                </Row>
            </Container>
        );
    }
    
    onChangeFormData(data) {
        this.setState({ ...data });
    }
}

ReactDOM.render(<App />, document.getElementById('app'));
/* Styles go here */