<html>
    <head>
        <script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
        <script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
        <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
        <script src="https://unpkg.com/styled-components/dist/styled-components.min.js"></script>
        <script src="https://unpkg.com/react-json-view@1.16.6/dist/main.js"></script>
        <script defer src="https://use.fontawesome.com/releases/v5.0.13/js/all.js" integrity="sha384-xymdQtn1n3lH2wcu0qhcdaOpQwyoarkgLVxC/wZ5q7h9gHtxICrpcaSUfygqZGOe" crossorigin="anonymous"></script>
        <link rel="stylesheet" type="text/css" href="http://fonts.googleapis.com/css?family=Lato">
        <style>
            body {
                font-family: 'Lato', serif;
                box-sizing: border-box;
                margin: 0;
                padding: 0;
            }

            h1 {
                margin: 0;
                padding: 0;
            }

            h2 {
                margin: 6px;
                padding: 0;
            }

            h3 {
                margin: 4px;
                padding: 0;
            }

            ul > li {
                margin-bottom: 6px;
            }

            label {
                padding-right: 15px;
            }

            button {
                margin: 5px;
                padding: 5px 10px;
                border-radius: 4px;
                box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23);
            }
        </style>
    </head>
<body>
    <div id="root">
        <h2>Loading DRF ReDocs...</h2>
    </div>

    <script type="text/babel">
        const endpoints = {{ endpoints | safe }};
        const djangoGreen = '#44B78B';
        const universalFetch = (function getCorrectFetchImpl() {
            return fetch;
        }());

        function main() {
            const ApiProxy = new _ApiProxy();
            const JsonView = reactJsonView.default; 
            const Styled = styled.default;

            const Row = Styled.div`
                display: flex;
                align-items: ${props => props.align || 'stretch'};
                justify-content: ${props => props.justify || 'flex-start'};
                flex-wrap: ${props => props.wrap ? 'wrap' : 'nowrap'};
                ${props => props.extraCss || ''};
            `

            class Method extends React.Component {
                constructor(props) {
                    super(props);
                    this.state = {
                        response: null,
                        error: null,
                        sending: false,
                    }
                }

                render() {
                    let {endpoint, method} = this.props;
                    let {response, error, sending} = this.state;
                    return (
                        <li>
                            <span style={ {paddingRight: 10} }>{method}</span>
                            <button onClick={() => this._doAction(method)} style={ {color: djangoGreen} }>Send</button>
                            <button onClick={() => this.setState({response: null, error: null})} style={ {color: 'grey'} }>Clear</button>
                            {
                                sending && <h4>Sending...</h4> || (
                                    <div>
                                        <h4>Response:</h4>
                                        { response && <JsonView src={response} /> }
                                        <h4>Error:</h4>
                                        { error && <JsonView src={error} /> }
                                    </div>
                                )
                            }
                        </li>
                    )
                }

                _doAction = method => {
                    let {endpoint, token, fields, urlFields, urlParams, encoding, getFiles} = this.props;

                    let path = endpoint.path;
                    if (urlParams) {
                        path = urlParams.reduce(
                            (acc, key) => {
                                return acc.replace(key, urlFields[key])
                            }, path
                        );
                    }

                    this.setState({sending: true, response: null, error: null});
                    if (method === 'GET') {
                        ApiProxy.get({
                            token,
                            path,
                        })
                            .then(response => {
                                this.setState({response, sending: false});
                            })
                            .catch(error => {
                                this.setState({error, sending: false});
                            })
                    } else if (method === 'POST') {
                        ApiProxy.post({
                            token,
                            path,
                            encoding,
                            data: fields,
                            files: getFiles()
                        })
                            .then(response => {
                                this.setState({response, sending: false});
                            })
                            .catch(error => {
                                this.setState({error, sending: false});
                            })
                    } else if (method === 'PUT') {
                        ApiProxy.put({
                            token,
                            path,
                            encoding,
                            data: fields,
                            files: getFiles()
                        })
                            .then(response => {
                                this.setState({response, sending: false});
                            })
                            .catch(error => {
                                this.setState({error, sending: false});
                            })
                    } else if (method === 'DELETE') {
                        ApiProxy.delete({
                            token,
                            path,
                        })
                            .then(response => {
                                this.setState({response, sending: false});
                            })
                            .catch(error => {
                                this.setState({error, sending: false});
                            })
                    }
                }
            }

            class Encoding extends React.Component {
                static Type = Styled.div`
                    margin: 6px 12px;
                    color: ${props => props.selected ? djangoGreen : '#ccc'}
                `;

                render() {
                    let {selected, onSelect} = this.props;
                    
                    return (
                        <Row align='center' justify='center' extraCss='margin: 0 auto; padding: 3px;'>
                            <h4>Encoding: </h4>
                            <Encoding.Type selected={selected === 'json'} onClick={() => onSelect('json')}>
                                json
                            </Encoding.Type>
                            <Encoding.Type selected={selected === 'form-data'} onClick={() => onSelect('form-data')}>
                                form-data 
                            </Encoding.Type>
                        </Row>
                    )
                }
            }

            class Endpoint extends React.Component {
                static Wrapper = Styled.div`
                    padding: 10px;
                    border-bottom: 1px solid #ccc;
                `;

                static ToggleCollapse = Styled.div`
                    width: 60px;
                    padding: 5px 10px;
                    border-radius: 15px;
                    background-color: ${djangoGreen};
                    color: white;
                    position: absolute;
                    top: -15px;
                    left: calc(50% - 40px);
                    text-align: center;
                `;

                constructor(props) {
                    super(props);

                    function getUrlParams(path) {
                        const pattern = /<([^>]+)>/g;
                        return path.match(pattern);
                    }

                    this.urlParams = getUrlParams(this.props.endpoint.path);

                    this.state = {
                        open: false,
                        fields: {},
                        urlFields: this.urlParams && this.urlParams.reduce(
                            (acc, key) => {
                                acc[key] = '';
                                return acc;
                            }, {} 
                        ),
                        encoding: 'json',
                    }

                    this.inputRefs = {};
                }

                render() {
                    let {endpoint, token} = this.props;
                    let {open, fields, urlFields, encoding} = this.state;

                    return (
                        <Endpoint.Wrapper>
                            <div onClick={() => this.setState({open: !open})}>
                                <h2 >{endpoint.path}</h2>
                                <h3 style={ {color: '#ccc'} }>{endpoint.doc}</h3>
                            </div>
                            {
                                open && (
                                    <div style={ {padding: 15, marginTop: 15, position: 'relative', border: `1px solid ${djangoGreen}`, borderRadius: 4} }>
                                        <Endpoint.ToggleCollapse onClick={() => this.setState({open: false})}>Close</Endpoint.ToggleCollapse>

                                        <Encoding selected={encoding} onSelect={encoding => this.setState({encoding})} />

                                        <h3>Url Params: {!this.urlParams && '---'}</h3>
                                            {this.urlParams && (
                                                <ul>
                                                    {this.urlParams.map(key => (
                                                       <li key={key}>
                                                           <label For={key}>{key.slice(1, -1)}:</label>
                                                           <input
                                                               name={key}
                                                               value={urlFields[key]}
                                                               onChange={e => {
                                                                   this.setState({
                                                                       urlFields: {
                                                                           ...urlFields,
                                                                           [key]: e.target.value
                                                                       }
                                                                   })
                                                               }}
                                                           />
                                                       </li>
                                                    ))}
                                                </ul>
                                            )}
                                        <h3>Params: {!endpoint.input && '---'}</h3>
                                        {endpoint.input && (
                                            <ul>
                                                {endpoint.input.map(({name: key, type}) => {
                                                    let inputProps = this._getInputProps({name: key, type}, fields);
                                                    return (
                                                        <li key={key}>
                                                            <label For={key}>{key}: </label>
                                                            <input {...inputProps} />
                                                            <span style={ {paddingLeft: 10, color: '#ccc', fontSize: '80%'} }>({type})</span>
                                                        </li>
                                                    )
                                                })}
                                            </ul>
                                        )}

                                        <h3>Methods:</h3>
                                        <ul>
                                            {endpoint.methods.filter(method => method !== 'OPTIONS').map(m => (
                                                <Method
                                                    method={m}
                                                    endpoint={endpoint}
                                                    fields={fields}
                                                    urlParams={this.urlParams}
                                                    urlFields={urlFields}
                                                    token={token}
                                                    encoding={encoding}
                                                    getFiles={this._getInputFiles}
                                                    key={m}
                                                />
                                            ))}
                                        </ul>
                                    </div>
                                )
                            }
                        </Endpoint.Wrapper>
                    )
                }

                _getInputProps = ({name, type}, fields) => {
                    let props = {
                        name,
                        type: 'text',
                        value: fields[name],
                        onChange: e => {
                            let value = null;
                            if (e.target.type === 'checkbox') {
                                value = e.target.checked;
                            } else if (e.target.type === 'number') {
                                value = parseInt(e.target.value, 10);
                            } else {
                                value = e.target.value;
                            }

                            this.setState({
                                fields: {
                                    ...fields,
                                    [name]: value 
                                }
                            })
                        }
                    };

                    switch (type) {
                        case 'EmailField':
                            props.type = 'email';
                            break;

                        case 'IntegerField':
                            props.type = 'number';
                            break;

                        case 'BooleanField':
                            delete props.value;
                            props.type = 'checkbox';
                            props.checked = fields[name] || false;
                            break;

                        case 'CharField':
                            if (name === 'password' || name === 'pass') {
                                props.type = 'password';
                            }
                            break;

                        case 'FileField':
                            return {
                                name,
                                type: 'file',
                                ref: fileInput => {
                                    this.inputRefs[name] = fileInput;
                                },
                            };
                    }

                    return props;
                }

                _getInputFiles = () => {
                    return Object.keys(this.inputRefs).reduce(
                        (acc, name) => {
                            let input = this.inputRefs[name];
                            acc[name] = input.files && input.files[0] || null;
                            return acc;
                        }, {}
                    )
                }
            }

            class App extends React.Component {
                static Wrapper = Styled.div`
                    background-color: #e0e0e0;
                    padding: 102px 0px 0px 0px;
               `;

                static Navbar = Styled.div`
                    background-color: ${djangoGreen};
                    position: fixed;
                    left: 0;
                    top: 0;
                    width: 100vw; 
                    height: 72px;
                    display: flex;
                    align-items: center;
                    padding: 0px 30px;
                    box-shadow: 0 6px 12px rgba(0,0,0,0.16), 0 6px 12px rgba(0,0,0,0.23);
                    z-index: 100;
                    & > h1 {
                        color: white;
                    }
                `

                static Footer = Styled.div`
                    margin-top: 30px;
                    padding: 60px;
                    background-color: white;
                    display: flex;
                    flex-direction: column;
                    align-items: center;
                    justify-content: center;
                    & > h3 {
                        color: #ccc;
                    }
                `

                static WithMinHeight = Styled.div`
                    min-height: calc(100vh - 360px);
                `

                static Content = Styled.div`
                    max-width: 800px;
                    margin: 0px auto;
                    margin-bottom: 20px;
                    padding: 15px;
                    background-color: white;
                    box-shadow: 0 6px 12px rgba(0,0,0,0.16), 0 6px 12px rgba(0,0,0,0.23);
                `

                constructor(props) {
                    super(props);
                    this.state = {
                        token: '',
                        filter: '',
                        auth: true,
                    }
                }

                render() {
                    let {token, filter, auth} = this.state;

                    let filteredEndpoints = endpoints.sort((a, b) => a.path > b.path ? 1 : -1).filter(
                        endpoint => {
                            if (filter) {
                                return endpoint.path.toLocaleLowerCase().indexOf(filter.toLocaleLowerCase()) > -1
                            }
                            return true;
                        }
                    );

                    return (
                        <App.Wrapper>
                            <App.Navbar>
                                <h1>DRF ReDocs</h1>
                                <a href='https://github.com/whitedogg13/django-rest-framework-redocs/' target='_blank'>
                                    <div style={ {fontSize: 28, color: 'white', marginLeft: 20} }>
                                        <i className="fab fa-github"></i>
                                    </div>
                                </a>
                            </App.Navbar>

                            <App.WithMinHeight>
                                <App.Content>
                                    <h2>AUTH</h2>
                                    <label>Token:</label>
                                    <input value={token} onChange={e => this.setState({token: e.target.value})} />
                                    <label style={ {marginLeft: 10} }>
                                        Enable:
                                        <input type='checkbox' checked={auth} onChange={e => this.setState({auth: e.target.checked})} />
                                    </label>
                                </App.Content>
    
                                <App.Content>
                                    <Row align='center'>
                                        <h2 style={ {marginRight: 10} }>Filter</h2>
                                        <input value={filter} onChange={e => this.setState({filter: e.target.value})} />
                                    </Row>
                                </App.Content>
     
                                <App.Content>
                                    {
                                        filteredEndpoints.length === 0 ? (
                                            <h2>No corresponding API</h2>
                                        ) : filteredEndpoints.map(
                                            endpoint => (
                                                <Endpoint 
                                                    key={endpoint.path}
                                                    endpoint={endpoint} 
                                                    token={auth && token || null}
                                                />
                                            )
                                        )
                                    }
                                </App.Content>
                            </App.WithMinHeight>

                            <App.Footer>
                                <h3>Build with ❤️ by <a href="https://www.revteltech.com" target="_blank">Revtel</a> & <a href="https://github.com/whitedogg13" target="_blank">whitedogg13</a></h3>
                                <h3>DRF ReDocs 2018</h3>
                            </App.Footer>
                        </App.Wrapper>
                    )
                }
            }

            ReactDOM.render(
                <App />,
                document.getElementById('root')
            );

            console.log(endpoints);
        }

        class _ApiProxy {
            _commonHeader = {
                'Accept': 'application/json',
                'Content-Type': 'application/json',
            }
        
            _buildEndpoint = ({path}) => path;

            _buildHeaders = ({token, extraHeaders, skipCommonHeader=false}) => {
                let authHeader = token ? {Authorization: `Token ${token}`} : {};
                if (skipCommonHeader) {
                    return authHeader;
                }
                return {
                    ...this._commonHeader,
                    ...authHeader,
                }
            };

            _buildBody = (data, files, encoding='json') => {
                console.log(files);
                if (encoding === 'json') {
                    return JSON.stringify(data);
                } else if (encoding === 'form-data') {
                    let formData = new FormData();
                    for (let key in data) {
                        formData.append(key, data[key]);
                    }
                    for (let key in files) {
                        formData.append(key, files[key]);
                    }
                    console.log('formData', formData);
                    return formData;
                }
            }
        
            get({path, extraHeaders={}, token}) {
                return universalFetch(
                    this._buildEndpoint({path}),
                    {
                        method: 'GET',
                        headers: this._buildHeaders({token, extraHeaders})
                    }
                )
                    .then(response => {
                        if (`${response.status}`[0] === '2') {
                            return response.json();
                        }
                        return Promise.reject({status: response.status});
                    })
            }
        
            post({path, extraHeaders = {}, token, data, files, encoding}) {
                let skipCommonHeader = encoding === 'form-data';
                return universalFetch(
                    this._buildEndpoint({path}),
                    {
                        method: 'POST',
                        headers: this._buildHeaders({token, extraHeaders, skipCommonHeader}),
                        body: this._buildBody(data, files, encoding)
                    }
                )
                    .then(response => {
                        if (`${response.status}`[0] === '2') {
                            return response.json();
                        }
                        return Promise.reject({status: response.status});
                    })
            }
        
            put({path, extraHeaders = {}, token, data, files, encoding}) {
                let skipCommonHeader = encoding === 'form-data';
                return universalFetch(
                    this._buildEndpoint({path}),
                    {
                        method: 'PUT',
                        headers: this._buildHeaders({token, extraHeaders, skipCommonHeader}),
                        body: this._buildBody(data, files, encoding)
                    }
                )
                    .then(response => {
                        if (`${response.status}`[0] === '2') {
                            return response.json();
                        }
                        return Promise.reject({status: response.status});
                    })
            }
        
            delete({path, extraHeaders = {}, token}) {
                return universalFetch(
                    this._buildEndpoint({path}),
                    {
                        method: 'DELETE',
                        headers: this._buildHeaders({token, extraHeaders}),
                    }
                )
                    .then(response => {
                        if (`${response.status}`[0] === '2') {
                            return response.json();
                        }
                        return Promise.reject({status: response.status});
                    })
            }
        }

        main();
    </script>
</body>
</html>
