import React from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import PropTypes from 'prop-types';

import { useLingui } from '@lingui/react';
import { Trans, t } from '@lingui/macro';
import makeStyles from '@mui/styles/makeStyles';
import Backdrop from '@mui/material/Backdrop';
import Button from '@mui/material/Button';
import CircularProgress from '@mui/material/CircularProgress';
import Divider from '@mui/material/Divider';
import Grid from '@mui/material/Grid';
import Link from '@mui/material/Link';
import Tab from '@mui/material/Tab';
import Tabs from '@mui/material/Tabs';
import TextField from '@mui/material/TextField';
import Typography from '@mui/material/Typography';

import * as helper from './helper';
import * as M from '../../utils/metadata';
import useInterval from '../../hooks/useInterval';
import BoxText from '../../misc/BoxText';
import DebugModal from '../../misc/modals/Debug';
import Dialog from '../../misc/modals/Dialog';
import EncodingSelect from '../../misc/EncodingSelect';
import H from '../../utils/help';
import NotifyContext from '../../contexts/Notify';
import Paper from '../../misc/Paper';
import PaperHeader from '../../misc/PaperHeader';
import PaperFooter from '../../misc/PaperFooter';
import Process from './Process';
import ProcessControl from '../../misc/controls/Process';
import ProcessModal from '../../misc/modals/Process';
import Services from './Services';
import TabPanel from '../../misc/TabPanel';
import TabsVerticalGrid from '../../misc/TabsVerticalGrid';

const useStyles = makeStyles((theme) => ({
	gridContainer: {
		marginTop: '0.5em',
		marginBottom: '1em',
	},
	link: {
		marginLeft: 10,
		wordWrap: 'anywhere',
	},
	serviceDescription: {
		margin: '1em 0em 1em 0em',
	},
	serviceIcon: {
		fontSize: '4rem!important',
		maxHeight: 64,
		marginTop: '-0.065em',
	},
}));

export default function Edit(props) {
	const classes = useStyles();
	const { i18n } = useLingui();
	const { channelid: _channelid, service: _service, index: _index } = useParams();
	const id = props.restreamer.GetEgressId(_service, _index);
	const navigate = useNavigate();
	const notify = React.useContext(NotifyContext);
	const [$ready, setReady] = React.useState(false);
	const [$settings, setSettings] = React.useState(M.getDefaultEgressMetadata());
	const [$sources, setSources] = React.useState([]);
	const [$tab, setTab] = React.useState('general');
	const [$progress, setProgress] = React.useState({});
	const [$processDetails, setProcessDetails] = React.useState({
		open: false,
		data: {
			prelude: [],
			log: [],
		},
	});
	const processLogTimer = React.useRef();
	const [$processDebug, setProcessDebug] = React.useState({
		open: false,
		data: '',
	});
	const [$unsavedChanges, setUnsavedChanges] = React.useState(false);
	const [$skills, setSkills] = React.useState(null);
	const [$metadata, setMetadata] = React.useState({
		name: '',
		description: '',
		license: '',
	});
	const [$deleteDialog, setDeleteDialog] = React.useState(false);
	const [$saving, setSaving] = React.useState(false);
	const [$service, setService] = React.useState(null);
	const [$serviceSkills, setServiceSkills] = React.useState(null);

	useInterval(async () => {
		await update(false);
	}, 1000);

	React.useEffect(() => {
		(async () => {
			await update(true);
		})();
		// eslint-disable-next-line react-hooks/exhaustive-deps
	}, []);

	const update = async (isFirst) => {
		const channelid = props.restreamer.SelectChannel(_channelid);
		if (channelid === '' || channelid !== _channelid) {
			navigate('/', { replace: true });
			return;
		}

		const proc = await props.restreamer.GetEgress(_channelid, id, ['state']);
		if (proc === null) {
			notify.Dispatch('warning', 'notfound:egress:' + _service, i18n._(t`Publication service not found`));
			navigate(`/${_channelid}`);
			return;
		}

		setProgress(proc.progress);

		if (isFirst === true) {
			const s = Services.Get(_service);
			if (s === null) {
				notify.Dispatch('warning', 'notfound:egress:' + _service, i18n._(t`Publication service not found`));
				navigate(`/${_channelid}/`);
				return null;
			}

			setService(s);

			const skills = await props.restreamer.Skills();
			setSkills(skills);

			const serviceSkills = helper.conflateServiceSkills(s.requires, skills);
			setServiceSkills(serviceSkills);

			const ingest = await props.restreamer.GetIngestMetadata(_channelid);
			setMetadata({
				...$metadata,
				name: ingest.meta.name,
				description: ingest.meta.description,
				license: ingest.license,
			});

			const sources = helper.createSourcesFromStreams(ingest.streams);
			setSources(sources);

			const settings = await props.restreamer.GetEgressMetadata(_channelid, id);

			const profiles = settings.profiles;
			profiles[0].video = helper.preselectProfile(profiles[0].video, 'video', ingest.streams, serviceSkills.codecs.video, skills.encoders.video);
			profiles[0].audio = helper.preselectProfile(profiles[0].audio, 'audio', ingest.streams, serviceSkills.codecs.audio, skills.encoders.audio);

			settings.profiles = profiles;
			settings.streams = M.createOutputStreams(sources, profiles);

			setSettings(settings);

			setReady(true);
		}
	};

	const handleServiceAction = async (action) => {
		let state = 'disconnected';

		if (action === 'connect') {
			await props.restreamer.StartEgress(_channelid, id);
			state = 'connecting';
		} else if (action === 'disconnect') {
			await props.restreamer.StopEgress(_channelid, id);
			state = 'disconnecting';
		} else if (action === 'reconnect') {
			await props.restreamer.StopEgress(_channelid, id);
			await props.restreamer.StartEgress(_channelid, id);
			state = 'connecting';
		}

		setProgress({
			...$progress,
			state: state,
		});
	};

	const handleServiceChange = (outputs, settings) => {
		if (!Array.isArray(outputs)) {
			outputs = [outputs];
		}

		setSettings({
			...$settings,
			outputs: outputs,
			settings: settings,
		});

		setUnsavedChanges(true);
	};

	const handleEncoding = (type) => (encoder, decoder, automatic) => {
		const profiles = $settings.profiles;

		profiles[0][type].encoder = encoder;
		profiles[0][type].decoder = decoder;

		setSettings({
			...$settings,
			profiles: profiles,
			streams: M.createOutputStreams($sources, profiles),
		});

		if (!automatic) {
			setUnsavedChanges(true);
		}
	};

	const handleServiceDone = async () => {
		setSaving(true);

		const [inputs, outputs] = helper.createInputsOutputs($sources, $settings.profiles, $settings.outputs);

		const [, err] = await props.restreamer.UpdateEgress(_channelid, id, inputs, outputs, $settings.control);
		if (err !== null) {
			setSaving(false);
			notify.Dispatch('error', 'save:egress:' + _service, i18n._(t`Failed to store publication service (${err.message})`));
			return;
		}

		await props.restreamer.SetEgressMetadata(_channelid, id, $settings);

		setSaving(false);

		notify.Dispatch('success', 'save:egress:' + _service, i18n._(t`The settings for "${$settings.name}" have been saved`));

		setUnsavedChanges(false);
	};

	const handleServiceName = (event) => {
		const name = event.target.value;

		setSettings({
			...$settings,
			name: name,
		});

		setUnsavedChanges(true);
	};

	const handleProcessControlChange = (control, automatic) => {
		setSettings({
			...$settings,
			control: {
				...$settings.control,
				process: control,
			},
		});

		if (automatic === false) {
			setUnsavedChanges(true);
		}
	};

	const handleServiceDeleteDialog = () => {
		setDeleteDialog(!$deleteDialog);
	};

	const handleServiceDelete = async () => {
		setSaving(true);

		const res = await props.restreamer.DeleteEgress(_channelid, id);
		if (res === false) {
			setSaving(false);
			notify.Dispatch('warning', 'delete:egress:' + _service, i18n._(t`The publication service "${$settings.name}" could not be deleted`));
			return;
		}

		setSaving(false);

		notify.Dispatch('success', 'delete:egress:' + _service, i18n._(t`The publication service "${$settings.name}" has been deleted`));

		navigate(`/${_channelid}`);
	};

	const handleAbort = () => {
		navigate(`/${_channelid}/`);
	};

	const handleChangeTab = (event, value) => {
		setTab(value);
	};

	const handleHelp = (topic) => () => {
		if (!topic) {
			H('publication-' + $tab);
			return;
		}

		H(topic);
	};

	const handleProcessDetails = async (event) => {
		event.preventDefault();

		const open = !$processDetails.open;
		let logdata = {
			prelude: [],
			log: [],
		};

		if (open === true) {
			const data = await props.restreamer.GetEgressLog(_channelid, id);
			if (data !== null) {
				logdata = data;
			}

			processLogTimer.current = setInterval(async () => {
				await updateProcessDetailsLog();
			}, 1000);
		} else {
			clearInterval(processLogTimer.current);
		}

		setProcessDetails({
			...$processDetails,
			open: open,
			data: logdata,
		});
	};

	const updateProcessDetailsLog = async () => {
		const data = await props.restreamer.GetEgressLog(_channelid, id);
		if (data !== null) {
			setProcessDetails({
				...$processDetails,
				open: true,
				data: data,
			});
		}
	};

	const handleProcessDebug = async (event) => {
		event.preventDefault();

		const show = !$processDebug.open;
		let data = '';

		if (show === true) {
			const debug = await props.restreamer.GetEgressDebug(_channelid, id);
			data = JSON.stringify(debug, null, 2);
		}

		setProcessDebug({
			...$processDebug,
			open: show,
			data: data,
		});
	};

	if ($ready === false) {
		return null;
	}

	const channelid = props.restreamer.SelectChannel(_channelid);
	if (channelid === '' || channelid !== _channelid) {
		navigate('/', { replace: true });
		return null;
	}

	const ServiceControl = $service.component;
	const ServiceIcon = $service.icon;

	const title = $settings.name.length === 0 ? $service.name : $settings.name;

	return (
		<React.Fragment>
			<Paper xs={12} md={10}>
				<PaperHeader
					title={
						<React.Fragment>
							<Trans>Edit: {title}</Trans>
						</React.Fragment>
					}
					onAbort={handleAbort}
					onHelp={handleHelp()}
				/>
				<Grid container spacing={1}>
					<TabsVerticalGrid>
						<Tabs orientation="vertical" variant="scrollable" value={$tab} onChange={handleChangeTab} className="tabs">
							<Tab className="tab" label={<Trans>General</Trans>} value="general" />
							<Tab className="tab" label={<Trans>Process control</Trans>} value="process" />
							<Tab className="tab" label={<Trans>Encoding</Trans>} value="encoding" />
						</Tabs>
						<TabPanel value={$tab} index="general" className="panel">
							<Grid container spacing={2}>
								<Grid item xs={12} md={2}>
									<ServiceIcon className={classes.serviceIcon} />
								</Grid>
								<Grid item xs={12} md={10}>
									<Typography variant="h1">{$service.name}</Typography>
									<Typography>v{$service.version}</Typography>
								</Grid>
								<Grid item xs={12} className={classes.serviceDescription}>
									<Typography>{$service.description}</Typography>
								</Grid>
								<Grid item xs={12}>
									<TextField
										variant="outlined"
										fullWidth
										label={<Trans>Service name</Trans>}
										value={$settings.name}
										onChange={handleServiceName}
									/>
								</Grid>
								<Grid item xs={12}>
									<ServiceControl settings={$settings.settings} skills={$serviceSkills} metadata={$metadata} onChange={handleServiceChange} />
								</Grid>
								<Grid item xs={12}>
									<Divider />
								</Grid>
								<Grid item xs={12}>
									<Typography>
										<Trans>Maintainer:</Trans>{' '}
										<Link color="secondary" target="_blank" href={$service.author.maintainer.link}>
											{$service.author.maintainer.name}
										</Link>
									</Typography>
								</Grid>
							</Grid>
						</TabPanel>
						<TabPanel value={$tab} index="process" className="panel">
							<Grid container spacing={2}>
								<Grid item xs={12} md={2}>
									<ServiceIcon className={classes.serviceIcon} />
								</Grid>
								<Grid item xs={12} md={10}>
									<Typography variant="h1">{$service.name}</Typography>
									<Typography>v{$service.version}</Typography>
								</Grid>
								<Grid item xs={12} className={classes.serviceDescription}>
									<Typography>{$service.description}</Typography>
								</Grid>
								<Grid item xs={12}>
									<Typography variant="h2">
										<Trans>Process</Trans>
									</Typography>
								</Grid>
								<Grid item xs={12}>
									<ProcessControl settings={$settings.control.process} onChange={handleProcessControlChange} />
								</Grid>
								<Grid item xs={12}>
									<Grid container spacing={1} className={classes.gridContainer}>
										<Grid item xs={12}>
											<Divider />
										</Grid>
										{$unsavedChanges === true && (
											<Grid item xs={12}>
												<BoxText>
													<Typography variant="body2" gutterBottom>
														<Trans>You have unsaved changes. Please save them before you can control the service again.</Trans>
													</Typography>
												</BoxText>
											</Grid>
										)}
										{$unsavedChanges === false && (
											<React.Fragment>
												<Grid item xs={12}>
													<Process onAction={handleServiceAction} progress={$progress} />
												</Grid>
												<Grid item xs={12} align="right">
													<Link color="textSecondary" href="#!" onClick={handleProcessDetails} className={classes.link}>
														<Trans>Process details</Trans>
													</Link>
													<Link color="textSecondary" href="#!" onClick={handleProcessDebug} className={classes.link}>
														<Trans>Process debug</Trans>
													</Link>
												</Grid>
											</React.Fragment>
										)}
									</Grid>
								</Grid>
								<Grid item xs={12}>
									<Divider />
								</Grid>
								<Grid item xs={12}>
									<Typography>
										<Trans>Maintainer:</Trans>{' '}
										<Link color="secondary" target="_blank" href={$service.author.maintainer.link}>
											{$service.author.maintainer.name}
										</Link>
									</Typography>
								</Grid>
							</Grid>
						</TabPanel>
						<TabPanel value={$tab} index="encoding" className="panel">
							<Grid container spacing={2}>
								<Grid item xs={12} md={2}>
									<ServiceIcon className={classes.serviceIcon} />
								</Grid>
								<Grid item xs={12} md={10}>
									<Typography variant="h1">{$service.name}</Typography>
									<Typography>v{$service.version}</Typography>
								</Grid>
								<Grid item xs={12} className={classes.serviceDescription}>
									<Typography>{$service.description}</Typography>
								</Grid>
								<Grid item xs={12}>
									<Typography variant="h2">
										<Trans>Encoding</Trans>
									</Typography>
								</Grid>
								<Grid item xs={12}>
									<Typography variant="subheading">
										<Trans>
											Passthrough (copy) should only be disabled if necessary. Each encoding requires additional CPU/GPU resources.
										</Trans>
									</Typography>
								</Grid>
								<Grid item xs={12}>
									<Typography variant="h4">
										<Trans>Video settings</Trans>
									</Typography>
								</Grid>
								<Grid item xs={12}>
									<EncodingSelect
										type="video"
										streams={$sources[0].streams}
										profile={$settings.profiles[0].video}
										codecs={$serviceSkills.codecs.video}
										availableEncoders={$skills.encoders.video}
										availableDecoders={$skills.decoders.video}
										onChange={handleEncoding('video')}
									/>
								</Grid>
								<Grid item xs={12}>
									<Typography variant="h4">
										<Trans>Audio settings</Trans>
									</Typography>
								</Grid>
								<Grid item xs={12}>
									<EncodingSelect
										type="audio"
										streams={$sources[0].streams}
										profile={$settings.profiles[0].audio}
										codecs={$serviceSkills.codecs.audio}
										availableEncoders={$skills.encoders.audio}
										availableDecoders={$skills.decoders.audio}
										onChange={handleEncoding('audio')}
									/>
								</Grid>
								<Grid item xs={12}>
									<Divider />
								</Grid>
								<Grid item xs={12}>
									<Typography>
										<Trans>Maintainer:</Trans>{' '}
										<Link color="secondary" target="_blank" href={$service.author.maintainer.link}>
											{$service.author.maintainer.name}
										</Link>
									</Typography>
								</Grid>
							</Grid>
						</TabPanel>
					</TabsVerticalGrid>
				</Grid>
				<PaperFooter
					buttonsLeft={
						<Button variant="outlined" color="default" onClick={handleAbort}>
							<Trans>Close</Trans>
						</Button>
					}
					buttonsRight={
						<React.Fragment>
							<Button variant="outlined" color="primary" disabled={$unsavedChanges === false || $saving === true} onClick={handleServiceDone}>
								<Trans>Save</Trans>
							</Button>
							<Button variant="outlined" color="secondary" disabled={$saving === true} onClick={handleServiceDeleteDialog}>
								<Trans>Delete</Trans>
							</Button>
						</React.Fragment>
					}
				/>
			</Paper>
			<ProcessModal
				open={$processDetails.open}
				onClose={handleProcessDetails}
				title={<Trans>Process details</Trans>}
				progress={$progress}
				logdata={$processDetails.data}
				onHelp={handleHelp('process-details')}
			/>
			<DebugModal
				open={$processDebug.open}
				onClose={handleProcessDebug}
				title={<Trans>Process debug report</Trans>}
				data={$processDebug.data}
				onHelp={handleHelp('process-report')}
			/>
			<Dialog
				open={$deleteDialog}
				onClose={handleServiceDeleteDialog}
				title={<Trans>Do you want to delete {title}?</Trans>}
				buttonsLeft={
					<Button variant="outlined" color="default" onClick={handleServiceDeleteDialog}>
						<Trans>Abort</Trans>
					</Button>
				}
				buttonsRight={
					<Button variant="outlined" color="secondary" onClick={handleServiceDelete}>
						<Trans>Delete</Trans>
					</Button>
				}
			>
				<Typography>
					<Trans>Deleting a publication service cannot be reversed. The publication stops immediately.</Trans>
				</Typography>
			</Dialog>
			<Backdrop open={$saving}>
				<CircularProgress color="inherit" />
			</Backdrop>
		</React.Fragment>
	);
}

Edit.defaultProps = {
	restreamer: null,
};

Edit.propTypes = {
	restreamer: PropTypes.object.isRequired,
};
