import React, {useState, useRef, useLayoutEffect, useReducer} from 'react';
import {removeIndex, replaceIndex, append, arrayOf, shallowClone, getUID, DistributiveOmit} from './util';
import './CreateLeague.css';
import twemoji from 'twemoji';

// CONSTS

const MAX_SUBLEAGUE_DIVISION_TOTAL = 22;
const MAX_TEAMS_PER_DIVISION = 12;

// STATE CLASSES

class LeagueStructureState {
	subleagues: SubleagueState[]

	constructor(subleagues: SubleagueState[] = []) {
		this.subleagues = subleagues;
	}
}

class SubleagueState {
	name: string
	divisions: DivisionState[]
	id: string|number

	constructor(divisions: DivisionState[] = []) {
		this.name = "";
		this.divisions = divisions;
		this.id = getUID();
	}
}

class DivisionState {
	name: string
	teams: TeamState[]
	id: string|number

	constructor() {
		this.name = "";
		this.teams = [];
		this.id = getUID();
	}
}

class TeamState {
	name: string
	id: string|number

	constructor(name: string = "") {
		this.name = name;
		this.id = getUID();
	}
}

// STRUCTURE REDUCER

type StructureReducerActions = 
	{type: 'remove_subleague', subleague_index: number} |
	{type: 'add_subleague'} |
	{type: 'rename_subleague', subleague_index: number, name: string} |
	{type: 'remove_divisions', division_index: number} |
	{type: 'add_divisions'} |
	{type: 'rename_division', subleague_index: number, division_index: number, name: string} |
	{type: 'remove_team', subleague_index: number, division_index: number, name:string} |
	{type: 'add_team', subleague_index:number, division_index:number, name:string}

function leagueStructureReducer(state: LeagueStructureState, action: StructureReducerActions): LeagueStructureState {
	switch (action.type) {
	case 'remove_subleague':
		return {subleagues: removeIndex(state.subleagues, action.subleague_index)};
	case 'add_subleague':
		return {subleagues: append(state.subleagues, new SubleagueState(
			arrayOf(state.subleagues[0].divisions.length, i => 
				new DivisionState()
			)
		))}
	case 'rename_subleague':
		return replaceSubleague(state, action.subleague_index, subleague => {
			let nSubleague = shallowClone(subleague);
			nSubleague.name = action.name;
			return nSubleague;
		});
	case 'remove_divisions':
		return {subleagues: state.subleagues.map(subleague => {
			let nSubleague = shallowClone(subleague);
			nSubleague.divisions = removeIndex(subleague.divisions, action.division_index)
			return nSubleague;
		})};
	case 'add_divisions':
		return {subleagues: state.subleagues.map(subleague => {
			let nSubleague = shallowClone(subleague);
			nSubleague.divisions = append(subleague.divisions, new DivisionState())
			return nSubleague;
		})};
	case 'rename_division':
		return replaceDivision(state, action.subleague_index, action.division_index, division => {
			let nDivision = shallowClone(division);
			nDivision.name = action.name;
			return nDivision; 
		});
	case 'remove_team':
		return replaceDivision(state, action.subleague_index, action.division_index, division => {
			let nDivision = shallowClone(division);
			nDivision.teams = removeIndex(division.teams, division.teams.findIndex(val => val.name === action.name));
			return nDivision; 
		});
	case 'add_team':
		return replaceDivision(state, action.subleague_index, action.division_index, division => {
			let nDivision = shallowClone(division);
			nDivision.teams = append(division.teams, new TeamState(action.name));
			return nDivision;
		});
	}
}

function replaceSubleague(state: LeagueStructureState, si: number, func: (val: SubleagueState) => SubleagueState) {
	return {subleagues: replaceIndex(state.subleagues, si, func(state.subleagues[si]))}
}

function replaceDivision(state: LeagueStructureState, si: number, di: number, func:(val: DivisionState) => DivisionState) {
	return replaceSubleague(state, si, subleague => {
		let nSubleague = shallowClone(subleague);
		nSubleague.divisions = replaceIndex(subleague.divisions, di, func(subleague.divisions[di]));
		return nSubleague;
	});
}

// OPTIONS REDUCER

class LeagueOptionsState {
	games_series = "3"
	intra_division_series = "8"
	inter_division_series = "16"
	inter_league_series = "8"
	top_postseason = "1"
	wildcards = "0"
}

type OptionsReducerActions =
	{type: 'set_games_series', value: string} |
	{type: 'set_intra_division_series', value: string} |
	{type: 'set_inter_division_series', value: string} |
	{type: 'set_inter_league_series', value: string} |
	{type: 'set_top_postseason', value: string} |
	{type: 'set_wildcards', value: string}

function LeagueOptionsReducer(state: LeagueOptionsState, action: OptionsReducerActions) {
	let newState = shallowClone(state);
	switch (action.type) {
	case 'set_games_series':
		newState.games_series = action.value;
		break;
	case 'set_intra_division_series':
		newState.intra_division_series = action.value;
		break;
	case 'set_inter_division_series':
		newState.inter_division_series = action.value;
		break;
	case 'set_inter_league_series':
		newState.inter_league_series = action.value;
		break;
	case 'set_top_postseason':
		newState.top_postseason = action.value;
		break;
	case 'set_wildcards':
		newState.wildcards = action.value;
		break;
	}
	return newState
}

// CREATE LEAGUE

let initLeagueStructure = {
	subleagues: [0, 1].map((val) => 
		new SubleagueState([0, 1].map((val) => 
			new DivisionState()
		))
	)
};

function CreateLeague() {
	let [name, setName] = useState("");
	let [showError, setShowError] = useState(false);
	let [nameExists, setNameExists] = useState(false);
	let [deletedTeams, setDeletedTeams] = useState(new Set<string>());
	let [createSuccess, setCreateSuccess] = useState(false);
	let [structure, structureDispatch] = useReducer(leagueStructureReducer, initLeagueStructure);
	let [options, optionsDispatch] = useReducer(LeagueOptionsReducer, new LeagueOptionsState());

	let self = useRef<HTMLDivElement | null>(null);	

	useLayoutEffect(() => {
		if (self.current) {
			twemoji.parse(self.current)
		}
	});

	let duplicateTeams = getDuplicateTeams(structure);

	let submit = () => {
		if (!validRequest(name, structure, options)) {
			setShowError(true);
		} else {
			let req = new XMLHttpRequest();
			let data = makeRequest(name, structure, options);
			req.open("POST", "/api/leagues", true);
			req.setRequestHeader("Content-type", "application/json");
			req.onreadystatechange = () => {
				if(req.readyState === 4) {
					if (req.status === 200) {
						setCreateSuccess(true);
					}
					if (req.status === 400) {
						let err = JSON.parse(req.response);
						switch (err.status) {
						case 'err_league_exists':
							setNameExists(true);
							break;
						case 'err_no_such_team':
							setDeletedTeams(new Set(err.cause));
							break;
						}
						setShowError(true);
					}
				}
			}
			req.send(data);
		}
	}

	if (createSuccess) {
		return( 
			<div className="cl_league_main" ref={self}>
				<div className="cl_confirm_box">
					League created succesfully!
				</div>
			</div>
		);
	}

	return (
		<div className="cl_league_main" ref={self}>
			<input type="text" className="cl_league_name" placeholder="League Name" value={name} onChange={(e) => {
				setName(e.target.value);
				setNameExists(false);
			}}/>
			<div className="cl_structure_err">{
				name === "" && showError ? "A name is required." : 
				nameExists && showError ? "A league by that name already exists" : 
				""
			}</div>
			<LeagueStructre 
				state={structure} 
				dispatch={structureDispatch} 
				deletedTeams={deletedTeams}
				duplicateTeams={duplicateTeams} 
				showError={showError}
			/>
			<div className="cl_league_options">
				<LeagueOptions 
					state={options} 
					dispatch={optionsDispatch} 
					showError={showError}
				/>
				<div className="cl_option_submit_box">
					<button className="cl_option_submit" onClick={submit}>Submit</button>
					<div className="cl_option_err">{
						!validRequest(name, structure, options) && showError ?
						"Cannot create league. Some information is missing or invalid." : ""
					}</div>
				</div>
			</div>
		</div>
	);
}

function makeRequest(name:string, structure: LeagueStructureState, options:LeagueOptionsState) {
	return JSON.stringify({
		name: name,
		structure: {
			subleagues: structure.subleagues.map(subleague => ({
				name: subleague.name,
				divisions: subleague.divisions.map(division => ({
					name: division.name,
					teams: division.teams.map(team => team.name)
				}))
			}))
		},
		games_per_series: Number(options.games_series),
		division_series: Number(options.intra_division_series),
		inter_division_series: Number(options.inter_division_series),
		inter_league_series: Number(options.inter_league_series),
		top_postseason: Number(options.top_postseason),
		wildcards: Number(options.wildcards)
	});
}

function validRequest(name:string, structure: LeagueStructureState, options:LeagueOptionsState) {
	return (
		name !== "" && 

		validNumber(options.games_series) && 
		validNumber(options.intra_division_series) && 
		validNumber(options.top_postseason) &&
		validNumber(options.wildcards, 0) &&

		structure.subleagues.length % 2 === 0 &&
		getDuplicateTeams(structure).size === 0 &&

		structure.subleagues.every((subleague, si) => 
			subleague.name !== "" &&
			!structure.subleagues.slice(0, si).some(val => val.name === subleague.name) &&
			subleague.divisions.every((division, di) => 
				division.name !== "" &&
				division.teams.length >= 2 &&
				division.teams.length <= MAX_TEAMS_PER_DIVISION &&
				!subleague.divisions.slice(0, di).some(val => val.name === division.name)
			)
		)
	)
}

function validNumber(value: string, min = 1) {
	return !isNaN(Number(value)) && Number(value) >= min;
}

function getDuplicateTeams(structure: LeagueStructureState) {
	return new Set(
		structure.subleagues.map(subleague => 
			subleague.divisions.map(division =>
				division.teams.map(team => team.name)
			).reduce((prev, curr) => prev.concat(curr), [])
		).reduce((prev, curr) => prev.concat(curr), [])
		.filter((val, i, arr) => arr.slice(0, i).indexOf(val) >= 0)
	)
}

// LEAGUE STRUCUTRE

function LeagueStructre(props: {
		state: LeagueStructureState, 
		dispatch: React.Dispatch<StructureReducerActions>, 
		deletedTeams: Set<string>, 
		duplicateTeams: Set<string>,
		showError: boolean
	}) {

	let nSubleagues = props.state.subleagues.length;
	let nDivisions = props.state.subleagues[0].divisions.length;
	return (
		<div className="cl_league_structure">
			<div className="cl_league_structure_scrollbox">
				<div className="cl_subleague_add_align">
					<div className="cl_league_structure_table">
						<SubleagueHeaders 
							subleagues={props.state.subleagues} 
							dispatch={props.dispatch} 
							showError={props.showError}
						/>
						<Divisions 
							subleagues={props.state.subleagues} 
							dispatch={props.dispatch} 
							deletedTeams={props.deletedTeams} 
							duplicateTeams={props.duplicateTeams}
							showError={props.showError}
						/>
					</div>
					{ (nSubleagues+1) * (nDivisions+1) < MAX_SUBLEAGUE_DIVISION_TOTAL ?
						<button className="cl_subleague_add" onClick={e => props.dispatch({type: 'add_subleague'})}>➕</button> :
						<div className="cl_subleague_add_filler"/>
					}
				</div>
			</div>
			<div className="cl_structure_err">{props.state.subleagues.length % 2 !== 0 && props.showError ? "Must have an even number of conferences." : ""}</div>
			{ nSubleagues * (nDivisions+2) < MAX_SUBLEAGUE_DIVISION_TOTAL ?
				<button className="cl_division_add" onClick={e => props.dispatch({type: 'add_divisions'})}>➕</button>:
				<div className="cl_division_add_filler"/>
			}
		</div>
	);
}

function SubleagueHeaders(props: {subleagues: SubleagueState[], dispatch: React.Dispatch<StructureReducerActions>, showError:boolean}) {
	return (
		<div className="cl_headers">
			<div key="filler" className="cl_delete_filler"/> 
			{props.subleagues.map((subleague, i) => {
				let err = 
					subleague.name === "" ?
						"A name is required." :
					props.subleagues.slice(0, i).some(val => val.name === subleague.name) ?
						"Each conference must have a different name." :
					"";

				return (
					<div key={subleague.id} className="cl_table_header">
						<div className="cl_subleague_bg">
							<SubleageHeader state={subleague} canDelete={props.subleagues.length > 1} dispatch={action => 
								props.dispatch(Object.assign({subleague_index: i}, action))
							}/>
							<div className="cl_structure_err">{props.showError ? err : ""}</div>
						</div>
					</div>
				)
			})}
		</div>
	);
}

function SubleageHeader(props: {state: SubleagueState, canDelete: boolean, dispatch:(action: DistributiveOmit<StructureReducerActions, 'subleague_index'>) => void}) {
	return (
		<div className="cl_subleague_header">
			<input type="text" className="cl_subleague_name" placeholder="Conference Name" value={props.state.name} onChange={e => 
				props.dispatch({type: 'rename_subleague', name: e.target.value})
			}/>
			{props.canDelete ? <button className="cl_subleague_delete" onClick={e => props.dispatch({type: 'remove_subleague'})}>➖</button> : null}
		</div>
	);
}

function Divisions(props: {
		subleagues: SubleagueState[], 
		dispatch: React.Dispatch<StructureReducerActions>, 
		deletedTeams: Set<string>, 
		duplicateTeams: Set<string>,
		showError: boolean
	}) {

	return (<>
		{props.subleagues[0].divisions.map((val, di) => (
			<div key={val.id} className="cl_table_row">
				<div key="delete" className="cl_delete_box">
					{props.subleagues[0].divisions.length > 1 ?
						<button className="cl_division_delete" onClick={e => props.dispatch({type: 'remove_divisions', division_index: di})}>➖</button> :
						null
					}
				</div>
				{props.subleagues.map((subleague, si) => (
					<div key={subleague.id} className="cl_division_cell">
						<div className="cl_subleague_bg">
							<Division 
								state={subleague.divisions[di]} 
								dispatch={action =>
									props.dispatch(Object.assign({subleague_index: si, division_index: di}, action))
								} 
								isDuplicate={subleague.divisions.slice(0, di).some(val => val.name === subleague.divisions[di].name)}
								deletedTeams={props.deletedTeams}
								duplicateTeams={props.duplicateTeams}
								showError={props.showError} 
							/>
						</div>
					</div>
				))}
			</div>
		))}
	</>);
}

function Division(props: {
		state: DivisionState, 
		dispatch: (action: DistributiveOmit<StructureReducerActions, 'subleague_index'|'division_index'>) => void, 
		isDuplicate: boolean,
		deletedTeams: Set<string>,
		duplicateTeams: Set<string>,
		showError: boolean
	}) {

	let [newName, setNewName] = useState("");
	let [searchResults, setSearchResults] = useState<string[]>([]);
	let newNameInput = useRef<HTMLInputElement>(null);
	let resultList = useRef<HTMLDivElement>(null);

	useLayoutEffect(() => {
		if (resultList.current) {
			twemoji.parse(resultList.current)
		}
	})

	let divisionErr = 
		props.state.name === "" ? 
			"A name is required." :
		props.isDuplicate ?
			"Each division in a conference must have a different name." :
		""

	let teamsErr = props.state.teams.length < 2 ? "Must have at least 2 teams." : "";

	return (
		<div className="cl_division">
			<div className="cl_division_name_box">
				<input type="text" className="cl_division_name" placeholder="Division Name" key="input" value={props.state.name} onChange={e => 
					props.dispatch({type: 'rename_division', name: e.target.value})
				}/>
				<div className="cl_structure_err cl_structure_err_div">{props.showError ? divisionErr : ""}</div>
			</div>
			{props.state.teams.map((team, i) => ( 
				<Team key={team.id}
					state={team} 
					dispatch={props.dispatch} 
					isDuplicate={props.duplicateTeams.has(team.name)} 
					isDeleted={props.deletedTeams.has(team.name)} 
					showError={props.showError}
				/>
			))}
			{props.state.teams.length < MAX_TEAMS_PER_DIVISION ? <>
				<div className="cl_team_add">
					<input type="text" className="cl_newteam_name" placeholder="Add team..." value={newName} ref={newNameInput}
						onChange={e => {
							if (e.target.value === "") {
								setSearchResults([]);
							} else {
								let params = new URLSearchParams({query: e.target.value, page_len: '5', page_num: '0'});
								fetch("/api/teams/search?" + params.toString())
									.then(response => response.json())
									.then(data => setSearchResults(data));
							}
							setNewName(e.target.value);
						}}/>
				</div>
				{searchResults.length > 0 && newName.length > 0 ? 
					(<div className="cl_search_list" ref={resultList}>
						{searchResults.map(result => 
							<div className="cl_search_result" key={result} onClick={e => {
								props.dispatch({type:'add_team', name: result});
								setNewName("");
								if (newNameInput.current) {
									newNameInput.current.focus();
								}
							}}>{result}</div>
						)}
					</div>): 
					null
				}</> :
				null
			}
			
			<div className="cl_structure_err cl_structure_err_teams">{props.showError ? teamsErr : ""}</div>
		</div>
	);
}

function Team(props: {
		state: TeamState, 
		dispatch: (action: DistributiveOmit<StructureReducerActions, 'subleague_index'|'division_index'>) => void,
		isDuplicate: boolean,
		isDeleted: boolean,
		showError: boolean
	}) {

	let errMsg = 
		props.isDeleted ?
			"This team was deleted" :
		props.isDuplicate ?
			"Each team in a league must be unique" :
		"";
	
	return (<>
		<div className="cl_team">
			<div className={"cl_team_name" + (errMsg && props.showError ? " cl_team_name_err" : "")}>{props.state.name}</div>
			<button className="cl_team_delete" onClick={e => props.dispatch({type:'remove_team', name: props.state.name})}>➖</button>
		</div>
		<div className="cl_structure_err cl_structure_err_team">{props.showError ? errMsg : ""}</div>
	</>);
}

// LEAGUE OPTIONS

function LeagueOptions(props: {state: LeagueOptionsState, dispatch: React.Dispatch<OptionsReducerActions>, showError: boolean}) {
	return (
		<div className="cl_option_main">
			<div className="cl_option_column">
				<NumberInput title="Number of games per series" value={props.state.games_series} setValue={(value: string) => 
					props.dispatch({type: 'set_games_series', value: value})} showError={props.showError}/>
				<NumberInput title="Number of teams from top of division to postseason" value={props.state.top_postseason} setValue={(value: string) => 
					props.dispatch({type: 'set_top_postseason', value: value})} showError={props.showError}/>
				<NumberInput title="Number of wildcards" value={props.state.wildcards} minValue={0} setValue={(value: string) => 
					props.dispatch({type: 'set_wildcards', value: value})} showError={props.showError}/>
			</div>
			<div className="cl_option_column">
				<NumberInput title="Number of series with each division opponent" value={props.state.intra_division_series} setValue={(value: string) => 
					props.dispatch({type: 'set_intra_division_series', value: value})} showError={props.showError}/>
				<NumberInput title="Number of inter-divisional series" value={props.state.inter_division_series} setValue={(value: string) => 
					props.dispatch({type: 'set_inter_division_series', value: value})} showError={props.showError}/>
				<NumberInput title="Number of inter-conference series" value={props.state.inter_league_series} setValue={(value: string) => 
					props.dispatch({type: 'set_inter_league_series', value: value})} showError={props.showError}/>
			</div>
		</div>
	);
}

function NumberInput(props: {title: string, value: string, setValue: (newVal: string) => void, showError: boolean, minValue? : number}) {
	let minValue = 1;
	if (props.minValue !== undefined) { 
		minValue = props.minValue
	}
	return (
		<div className="cl_option_box">
			<div className="cl_option_label">{props.title}</div>
			<input className="cl_option_input" type="number" min={minValue} value={props.value} onChange={e => props.setValue(e.target.value)}/>
			<div className="cl_option_err">{(isNaN(Number(props.value)) || Number(props.value) < minValue) && props.showError ? "Must be a number greater than " + minValue : ""}</div>
		</div>
	);
}

export default CreateLeague;