// @flow
import * as React from 'react';
import { Link } from 'react-router-dom';
import {
	Container,
	Row,
	Col,
	Button,
	ButtonGroup,
	Form,
	FormGroup,
	Label,
	Input,
	InputGroup,
	FormText,
	Collapse,
	Card,
	CardHeader,
	CardBody,
	TabContent, TabPane,
	Navbar,
	Nav, NavItem, NavLink, CardTitle, CardText,
	FormFeedback,
	Alert,
	InputGroupAddon,
	Badge,
	Modal, ModalBody, ModalHeader, ModalFooter,
	UncontrolledDropdown, UncontrolledButtonDropdown, DropdownToggle, DropdownMenu, DropdownItem,
} from 'reactstrap';

import CacheInput from '../CacheInput.jsx';
import DeleteInputBtn from '../DeleteInputBtn.jsx';
import { generateNewKey, copyObj } from '@helpers';
import { toUrl } from '@helpers/api';
import type { BlockType } from '@helpers/toolchainTypes';

type Props = {
	// is the modal active?
	active: boolean,
	// block data
	data: BlockType,
	// name of all blocks
	blockNames: string[],
	// possible channels the block can be sync'd to (an obj w keys as channel names & vals as colours)
	possibleChannels: { [string]: string },
	// func to close modal
	toggle: () => any,
	// delete the block
	deleteBlock: () => any,
	// updates the block info
	updateBlockData: (data: ModalCache) => any,
	// creates an Algorithm based on the current block given the new alg's name
	createAlgorithmFromBlock: (algName: string) => any,
};

export type ModalAction = 'delete' | 'add' | 'change';

type IO = { name: string, action: ?ModalAction, original: string };

export type ModalCache = {
	name: string,
	synchronized_channel: ?string,
	inputs: ?IO[],
	outputs: ?IO[],
};

type State = {
	// name, synchronized_channel, inputs, outputs
	cache: ModalCache,
	// name of the algorithm to create using this block's data
	algName: string,
	// are there unsaved changes for the block?
	unsavedChanges: boolean,
	// whether the alg creation was successful or not
	wasSuccessful: ?boolean,
};

const newCacheFromData = (data: BlockType): ModalCache => {
	return {
		synchronized_channel: data.synchronized_channel || undefined,
		name: data.name,
		inputs: data.inputs ? data.inputs.map(i => ({ name: i, action: 'change', original: i })) : undefined,
		outputs: data.outputs ? data.outputs.map(i => ({ name: i, action: 'change', original: i })) : undefined,
	};
};

const getCurrentObjs = (arr: any) => arr
.filter((o, i, os) => o.action !== 'delete' && !os.slice(i).find(p => p.action === 'delete' && p.name === o.name));

const getCurrentNames = (arr: any) => getCurrentObjs(arr).map(o => o.name);

class ToolchainModal extends React.Component<Props, State> {
	constructor(props: Props){
		super(props);
	}

	state = {
		cache: newCacheFromData(this.props.data),
		algName: '/',
		unsavedChanges: false,
		wasSuccessful: null,
	}

	componentDidUpdate = (prevProps: Props, prevState: State) => {
		if(this.props.data && prevProps.data !== this.props.data){
			this.setState({ cache: newCacheFromData(this.props.data) });
		}
	}

	updateAlgName = (newName: string) => {
		this.setState({ algName: newName });
	}

	addIO = (io: boolean) => {
		const arr = io ? 'inputs' : 'outputs';
		const newKey = generateNewKey(io ? 'input' : 'output', getCurrentNames(this.state.cache[arr]));
		this.setState({
			cache: {
				...this.state.cache,
				[arr]: [
					...this.state.cache[arr],
					{
						name: newKey,
						action: 'add',
						original: newKey,
					}
				]
			},
			unsavedChanges: true,
		});
	}

	deleteIO = (io: boolean, name: string) => {
		const arr = io ? 'inputs' : 'outputs';
		this.setState({
			cache: {
				...this.state.cache,
				[arr]: [
					...this.state.cache[arr],
					{
						name: name,
						action: 'delete',
						original: name,
					}
				]
			},
			unsavedChanges: true,

		});
	}

	changeIO = (io: boolean, oldObj: IO, newName: string) => {
		const arr = io ? 'inputs' : 'outputs';
		const newObj = {...oldObj, name: newName};
		const newArr = this.state.cache[arr].map(o => {
			if(o.original === oldObj.original){
				return newObj;
			} else {
				return o;
			}
		});

		this.setState({
			cache: {
				...this.state.cache,
				[arr]: newArr,
			},
			unsavedChanges: true,

		});
	}

	changeName = (newName: string) => {
		this.setState({
			cache: {
				...this.state.cache,
				name: newName,
			},
			unsavedChanges: newName !== this.props.data.name,

		});
	}

	changeChannel = (channel: string) => {
		//console.log(`changing channel from ${ this.state.cache.synchronized_channel } to ${ channel }`);
		this.setState({
			cache: {
				...this.state.cache,
				synchronized_channel: channel,
			},
			unsavedChanges: channel !== this.props.data.synchronized_channel,

		});
	}

	saveBlock = () => {
		this.props.updateBlockData(this.state.cache);
		this.setState({ unsavedChanges: false });
	}

	// creates an Algorithm from the (potentially unsaved) block data
	// and gives the user a link to go edit it in a new tab
	createAlgorithmFromBlock = async () => {
		const algName = `${ this.state.algName }/1`;
		try {
			await this.props.createAlgorithmFromBlock(algName);
			this.setState({ wasSuccessful: true });
		} catch(e) {
			this.setState({ wasSuccessful: false });
		}
		//window.open(, '_blank');
	}

	resetCaches = () => {
		this.setState({ cache: newCacheFromData(this.props.data), unsavedChanges: false, algName: '/', wasSuccessful: null });
	}

	renderAlgorithmCreator = () => {
		const segs = this.state.algName.split('/');
		const user = segs[0];
		const name = segs[1];
		const unsavedChanges = this.state.unsavedChanges;
		const algStr = user == '' || name == '' ? '' : `"${ this.state.algName }"`;

		return (
			<React.Fragment>
				<Alert color='secondary'>
					<Label>
						Create an Algorithm from this Block
					</Label>
					<Row>
						<Col sm='4 mr-0 pr-0'>
							<CacheInput
								fieldTest
								value={user}
								onChange={(e) => this.updateAlgName(`${ e.target.value }/${ name }`)}
								placeholder={'Username...'}
								disabled={unsavedChanges}
							/>
						</Col>
						<Col sm='4 mr-0 pr-0'>
							<CacheInput
								fieldTest
								value={name}
								onChange={(e) => this.updateAlgName(`${ user }/${ e.target.value }`)}
								placeholder={'Name...'}
								disabled={unsavedChanges}
							/>
						</Col>
						<Col sm='4'>
							<Button
								block
								color='secondary'
								onClick={() => {
									this.createAlgorithmFromBlock();
								}}
								disabled={user == '' || name == '' || unsavedChanges}
							>
								Create { algStr }
							</Button>
						</Col>
					</Row>
					{ unsavedChanges && user !== '' && name !== '' &&
							<Row>
								<Col>
									<span className='text-danger'>
										Please save your changes before creating an algorithm!
									</span>
								</Col>
							</Row>
					}
					{ this.state.wasSuccessful &&
							<Row>
								<Col>
									<span>
										The algorithm was created!{' '}
										<Link
											rel="noopener noreferrer"
											target='_blank'
											to={`/algorithm/${ this.state.algName }/1`}
										>
											Edit it here
										</Link>
										.
										If you do not see it, please refresh your browser tab or window.
									</span>
								</Col>
							</Row>
					}
					{ this.state.wasSuccessful === false &&
							<Row>
								<Col>
									<span className='text-danger'>
										The algorithm could not be created. Please create it manually.
									</span>
								</Col>
							</Row>
					}

				</Alert>
			</React.Fragment>
		);
	}

	render = () => {
		const data = this.state.cache;
		if(!data)
			return null;

		const hasInputs = Array.isArray(data.inputs);
		const hasOutputs = Array.isArray(data.outputs);
		// TODO: right now this is just an escape hatch for channel changing not propogating throughout the network. this needs to be fixed
		const channelSelectDisabled = false; //Object.keys(this.props.possibleChannels).length === 1;

		const currChannel: string = data.synchronized_channel || '';

		const formatChannel = (channel: string) => {
			return (
				<span>
					<span style={{backgroundColor: this.props.possibleChannels[channel] || '#000000'}}>
						&nbsp;&nbsp;&nbsp;&nbsp;
					</span>
					&nbsp;
					{ channel === '' ? '<none>' : channel }
				</span>
			);
		};

		const inputNames = hasInputs ? getCurrentNames(data.inputs) : [];
		const outputNames = hasOutputs ? getCurrentNames(data.outputs) : [];
		const inputDup = inputNames.slice().sort().find((n, i, ns) => i !== 0 && n === ns[i - 1]);
		const outputDup = outputNames.slice().sort().find((n, i, ns) => i !== 0 && n === ns[i - 1]);
		const hasInputDups = inputDup != undefined;
		const hasOutputDups = outputDup != undefined;

		return (
			<Modal
				size='lg'
				isOpen={this.props.active}
				toggle={this.props.toggle}
				fade={true}
				onOpened={e => {
					const f = document.querySelector('#tcModalInitFocus');
					if(f && f.focus)
						f.focus();
					this.resetCaches();
				}}
			>
				<ModalHeader toggle={this.props.toggle}>
					Editing <pre className='preInline'>{data.name}</pre>
					{' '}
				</ModalHeader>
				<ModalBody>
					<Form>
						<FormGroup>
							{
								hasInputs && this.renderAlgorithmCreator()
							}
						</FormGroup>
						<FormGroup row>
							<Col>
								<Label>Block Name</Label>
								<CacheInput
									delay={200}
									id='tcModalInitFocus'
									value={data.name || ''}
									fieldTest={true}
									onChange={(e) => {
										this.changeName(e.target.value);
									}}
									validateFunc={(str) => {
										if(str === this.props.data.name){
											return true;
										}
										if(this.props.blockNames.includes(str)){
											return <span>This name is already taken by an existing block</span>;
										}
										if(str.includes('-')){
											return <span>{`Cannot include the dash ("-") character`}</span>;
										}
										return true;
									}}
								/>
							</Col>
							{ data.synchronized_channel !== undefined
								&&
									<Col>
										<Label>Synchronized channel</Label>
										<div>
											<UncontrolledDropdown
												id='channelSelector'
												disabled={channelSelectDisabled}
											>
												<DropdownToggle caret outline block>
													{ formatChannel(currChannel || '') }
												</DropdownToggle>
												<DropdownMenu>
													{
														Array.from(new Set([...Object.keys(this.props.possibleChannels), currChannel || '']))
														.filter(c => c !== '')
														.map((c, i) =>
															<DropdownItem
																id={`channel-${ c }`}
																key={i}
																onClick={e => this.changeChannel(c)}
															>
																{ formatChannel(c) }
															</DropdownItem>
														)
													}
												</DropdownMenu>
											</UncontrolledDropdown>
										</div>
									</Col>
							}
						</FormGroup>
						<Row>
							{
								hasInputs &&
									<Col sm={hasOutputs ? 6 : 12}>
										<Label>Inputs</Label>
										{
											getCurrentObjs(data.inputs)
											.map((obj, i, inputs) => {
												const input = obj.name;
												return (
													<FormGroup
														key={i}
													>
														<CacheInput
															value={input}
															fieldTest={true}
															onChange={(e) => {
																this.changeIO(true, obj, e.target.value);
															}}
															invalid={input === inputDup}
														>
															<DeleteInputBtn
																deleteFunc={e => {
																	this.deleteIO(true, input);
																}}
															/>
														</CacheInput>
													</FormGroup>
												);
											})
										}
										<Button
											onClick={e => {
												this.addIO(true);
											}}
										>
											Add Input
										</Button>
									</Col>
							}
							{
								hasOutputs &&
									<Col sm={hasInputs ? 6 : 12}>
										<Label>Outputs</Label>
										{
											getCurrentObjs(data.outputs)
											.map((obj, i, outputs) => {
												const output = obj.name;
												return (
													<FormGroup
														key={i}
													>
														<CacheInput
															value={output}
															fieldTest={true}
															onChange={(e) => {
																this.changeIO(false, obj, e.target.value);
															}}
														>
															<DeleteInputBtn
																deleteFunc={e => {
																	this.deleteIO(false, output);
																}}
															/>
														</CacheInput>
													</FormGroup>
												);
											})
										}
										<Button
											onClick={e => {
												this.addIO(false);
											}}
										>
											Add Output
										</Button>
									</Col>
							}
						</Row>
					</Form>
				</ModalBody>
				<ModalFooter>
					<Button color='danger'
						onClick={(e) => {
							this.props.deleteBlock();
							this.props.toggle();
						}}
					>
						Delete Block
					</Button>
					<Button color='success'
						disabled={hasInputDups || hasOutputDups}
						onClick={(e) => {
							this.saveBlock();
							this.props.toggle();
						}}
					>
						Save Changes
					</Button>
				</ModalFooter>
			</Modal>
		);
	}
}

export default ToolchainModal;
