import { Canvas, CircleMenu, ButtonInput, ContextMenu, Loader } from '../libs/flow.module.js';
import { StandardMaterialEditor } from './materials/StandardMaterialEditor.js';
import { OperatorEditor } from './math/OperatorEditor.js';
import { NormalizeEditor } from './math/NormalizeEditor.js';
import { InvertEditor } from './math/InvertEditor.js';
import { LimiterEditor } from './math/LimiterEditor.js';
import { DotEditor } from './math/DotEditor.js';
import { PowerEditor } from './math/PowerEditor.js';
import { TrigonometryEditor } from './math/TrigonometryEditor.js';
import { FloatEditor } from './inputs/FloatEditor.js';
import { Vector2Editor } from './inputs/Vector2Editor.js';
import { Vector3Editor } from './inputs/Vector3Editor.js';
import { Vector4Editor } from './inputs/Vector4Editor.js';
import { SliderEditor } from './inputs/SliderEditor.js';
import { ColorEditor } from './inputs/ColorEditor.js';
import { BlendEditor } from './display/BlendEditor.js';
import { UVEditor } from './accessors/UVEditor.js';
import { PositionEditor } from './accessors/PositionEditor.js';
import { NormalEditor } from './accessors/NormalEditor.js';
import { TimerEditor } from './utils/TimerEditor.js';
import { OscillatorEditor } from './utils/OscillatorEditor.js';
import { CheckerEditor } from './procedural/CheckerEditor.js';
import { EventDispatcher } from 'three';

export const ClassLib = {
	'StandardMaterialEditor': StandardMaterialEditor,
	'OperatorEditor': OperatorEditor,
	'NormalizeEditor': NormalizeEditor,
	'InvertEditor': InvertEditor,
	'LimiterEditor': LimiterEditor,
	'DotEditor': DotEditor,
	'PowerEditor': PowerEditor,
	'TrigonometryEditor': TrigonometryEditor,
	'FloatEditor': FloatEditor,
	'Vector2Editor': Vector2Editor,
	'Vector3Editor': Vector3Editor,
	'Vector4Editor': Vector4Editor,
	'SliderEditor': SliderEditor,
	'ColorEditor': ColorEditor,
	'BlendEditor': BlendEditor,
	'UVEditor': UVEditor,
	'PositionEditor': PositionEditor,
	'NormalEditor': NormalEditor,
	'TimerEditor': TimerEditor,
	'OscillatorEditor': OscillatorEditor,
	'CheckerEditor': CheckerEditor
};

export class NodeEditor extends EventDispatcher {

	constructor() {

		super();

		const domElement = document.createElement( 'flow' );
		const canvas = new Canvas();

		domElement.appendChild( canvas.dom );

		this.canvas = canvas;
		this.domElement = domElement;

		this.nodesContext = null;
		this.examplesContext = null;

		this._initMenu();
		this._initNodesContext();
		this._initExamplesContext();

	}

	add( node ) {

		this.canvas.add( node );

		return this;

	}

	get nodes() {

		return this.canvas.nodes;

	}

	newProject() {

		this.canvas.clear();

		this.dispatchEvent( { type: 'new' } );

	}

	loadJSON( json ) {

		this.canvas.clear();

		this.canvas.deserialize( json );

		this.dispatchEvent( { type: 'load' } );

	}

	_initMenu() {

		const menu = new CircleMenu();

		const menuButton = new ButtonInput().setIcon( 'ti ti-menu-2' );
		const examplesButton = new ButtonInput().setIcon( 'ti ti-file-symlink' ).setToolTip( 'Examples' );
		const newButton = new ButtonInput().setIcon( 'ti ti-file' ).setToolTip( 'New' );
		const openButton = new ButtonInput().setIcon( 'ti ti-upload' ).setToolTip( 'Open' );
		const saveButton = new ButtonInput().setIcon( 'ti ti-download' ).setToolTip( 'Save' );

		const hideContext = () => {

			this.examplesContext.hide();
			this.nodesContext.hide();

		};

		menuButton.onClick( () => {

			this.nodesContext.show( 60, 50 );

		} );

		examplesButton.onClick( () => {

			this.examplesContext.show( 60, 175 );

		} );

		newButton.onClick( () => {

			hideContext();

			this.newProject();

		} );

		openButton.onClick( () => {

			hideContext();

			const input = document.createElement( 'input' );
			input.type = 'file';

			input.onchange = e => {

				const file = e.target.files[ 0 ];

				const reader = new FileReader();
				reader.readAsText( file, 'UTF-8' );

				reader.onload = readerEvent => {

					const loader = new Loader( Loader.OBJECTS );
					const json = loader.parse( JSON.parse( readerEvent.target.result ), ClassLib );

					this.loadJSON( json );

				};

			};

			input.click();

		} );

		saveButton.onClick( () => {

			hideContext();

			const json = JSON.stringify( this.canvas.toJSON() );

			const a = document.createElement( 'a' );
			const file = new Blob( [ json ], { type: 'text/plain' } );

			a.href = URL.createObjectURL( file );
			a.download = 'node_editor.json';
			a.click();

		} );

		menu.add( menuButton )
			.add( newButton )
			.add( examplesButton )
			.add( openButton )
			.add( saveButton );

		this.domElement.appendChild( menu.dom );

		this.menu = menu;

	}

	_initExamplesContext() {

		const context = new ContextMenu();

		//**************//
		// MAIN
		//**************//

		const onClickExample = async ( button ) => {

			this.examplesContext.hide();

			const filename = button.getExtra();

			const loader = new Loader( Loader.OBJECTS );
			const json = await loader.load( `./jsm/node-editor/examples/${filename}.json`, ClassLib );

			this.loadJSON( json );

		};

		const addExample = ( context, name, filename = null ) => {

			filename = filename || name.replaceAll( ' ', '-' ).toLowerCase();

			context.add( new ButtonInput( name )
				.setIcon( 'ti ti-file-symlink' )
				.onClick( onClickExample )
				.setExtra( filename )
			);

		};

		//**************//
		// EXAMPLES
		//**************//

		const basicContext = new ContextMenu();
		const advancedContext = new ContextMenu();

		addExample( basicContext, 'Animate UV' );
		addExample( basicContext, 'Fake top light' );
		addExample( basicContext, 'Oscillator color' );

		addExample( advancedContext, 'Rim' );

		//**************//
		// MAIN
		//**************//

		context.add( new ButtonInput( 'Basic' ), basicContext );
		context.add( new ButtonInput( 'Advanced' ), advancedContext );

		this.domElement.appendChild( context.dom );

		this.examplesContext = context;

	}

	_initNodesContext() {

		const context = new ContextMenu( this.domElement );

		let isContext = false;
		let contextPosition = {};

		const add = ( node ) => {

			const canvas = this.canvas;
			const canvasRect = canvas.rect;

			if ( isContext ) {

				node.setPosition(
					contextPosition.x,
					contextPosition.y
				);

			} else {

				const defaultOffsetX = 350 / 2;
				const defaultOffsetY = 20;

				node.setPosition(
					( canvas.relativeX + ( canvasRect.width / 2 ) ) - defaultOffsetX,
					( canvas.relativeY + ( canvasRect.height / 2 ) ) - defaultOffsetY
				);

			}

			context.hide();

			this.add( node );

			this.canvas.select( node );

			isContext = false;

		};

		context.onContext( () => {

			isContext = true;

			const { relativeClientX, relativeClientY } = this.canvas;

			contextPosition.x = relativeClientX;
			contextPosition.y = relativeClientY;

		} );

		//**************//
		// INPUTS
		//**************//

		const inputsContext = new ContextMenu();

		const sliderInput = new ButtonInput( 'Slider' ).setIcon( 'ti ti-adjustments-horizontal' )
			.onClick( () => add( new SliderEditor() ) );

		const floatInput = new ButtonInput( 'Float' ).setIcon( 'ti ti-box-multiple-1' )
			.onClick( () => add( new FloatEditor() ) );

		const vector2Input = new ButtonInput( 'Vector 2' ).setIcon( 'ti ti-box-multiple-2' )
			.onClick( () => add( new Vector2Editor() ) );

		const vector3Input = new ButtonInput( 'Vector 3' ).setIcon( 'ti ti-box-multiple-3' )
			.onClick( () => add( new Vector3Editor() ) );

		const vector4Input = new ButtonInput( 'Vector 4' ).setIcon( 'ti ti-box-multiple-4' )
			.onClick( () => add( new Vector4Editor() ) );

		const colorInput = new ButtonInput( 'Color' ).setIcon( 'ti ti-palette' )
			.onClick( () => add( new ColorEditor() ) );

		//const mapInput = new ButtonInput( 'Map' ).setIcon( 'ti ti-photo' );
		//const cubeMapInput = new ButtonInput( 'Cube Map' ).setIcon( 'ti ti-box' );
		//const integerInput = new ButtonInput( 'Integer' ).setIcon( 'ti ti-list-numbers' );

		inputsContext
			.add( sliderInput )
			.add( floatInput )
			.add( vector2Input )
			.add( vector3Input )
			.add( vector4Input )
			.add( colorInput );

		//**************//
		// MATH
		//**************//

		const mathContext = new ContextMenu();

		const operatorsNode = new ButtonInput( 'Operator' ).setIcon( 'ti ti-math-symbols' )
			.onClick( () => add( new OperatorEditor() ) );

		const normalizeNode = new ButtonInput( 'Normalize' ).setIcon( 'ti ti-fold' )
			.onClick( () => add( new NormalizeEditor() ) );

		const invertNode = new ButtonInput( 'Invert' ).setToolTip( 'Negate' ).setIcon( 'ti ti-flip-vertical' )
			.onClick( () => add( new InvertEditor() ) );

		const limiterNode = new ButtonInput( 'Limiter' ).setToolTip( 'Min / Max' ).setIcon( 'ti ti-arrow-bar-to-up' )
			.onClick( () => add( new LimiterEditor() ) );

		const dotNode = new ButtonInput( 'Dot Product' ).setIcon( 'ti ti-arrows-up-left' )
			.onClick( () => add( new DotEditor() ) );

		const powNode = new ButtonInput( 'Power' ).setIcon( 'ti ti-arrow-up-right' )
			.onClick( () => add( new PowerEditor() ) );

		const triNode = new ButtonInput( 'Trigonometry' ).setToolTip( 'Sin / Cos / Tan' ).setIcon( 'ti ti-wave-sine' )
			.onClick( () => add( new TrigonometryEditor() ) );

		mathContext
			.add( operatorsNode )
			.add( invertNode )
			.add( limiterNode )
			.add( dotNode )
			.add( powNode )
			.add( triNode )
			.add( normalizeNode );

		//**************//
		// ACCESSORS
		//**************//

		const accessorsContext = new ContextMenu();

		const uvNode = new ButtonInput( 'UV' ).setIcon( 'ti ti-details' )
			.onClick( () => add( new UVEditor() ) );

		const positionNode = new ButtonInput( 'Position' ).setIcon( 'ti ti-hierarchy' )
			.onClick( () => add( new PositionEditor() ) );

		const normalNode = new ButtonInput( 'Normal' ).setIcon( 'ti ti-fold-up' )
			.onClick( () => add( new NormalEditor() ) );

		accessorsContext
			.add( uvNode )
			.add( positionNode )
			.add( normalNode );

		//**************//
		// PROCEDURAL
		//**************//

		const proceduralContext = new ContextMenu();

		const checkerNode = new ButtonInput( 'Checker' ).setIcon( 'ti ti-border-outer' )
			.onClick( () => add( new CheckerEditor() ) );

		proceduralContext
			.add( checkerNode );

		//**************//
		// DISPLAY
		//**************//

		const displayContext = new ContextMenu();

		const blendNode = new ButtonInput( 'Blend' ).setIcon( 'ti ti-layers-subtract' )
			.onClick( () => add( new BlendEditor() ) );

		displayContext
			.add( blendNode );

		//**************//
		// UTILS
		//**************//

		const utilsContext = new ContextMenu();

		const timerNode = new ButtonInput( 'Timer' ).setIcon( 'ti ti-clock' )
			.onClick( () => add( new TimerEditor() ) );

		const oscNode = new ButtonInput( 'Oscillator' ).setIcon( 'ti ti-wave-sine' )
			.onClick( () => add( new OscillatorEditor() ) );

		utilsContext
			.add( timerNode )
			.add( oscNode );

		//**************//
		// MAIN
		//**************//

		context.add( new ButtonInput( 'Inputs' ).setIcon( 'ti ti-forms' ), inputsContext );
		context.add( new ButtonInput( 'Accessors' ).setIcon( 'ti ti-vector-triangle' ), accessorsContext );
		context.add( new ButtonInput( 'Display' ).setIcon( 'ti ti-brightness' ), displayContext );
		context.add( new ButtonInput( 'Math' ).setIcon( 'ti ti-calculator' ), mathContext );
		context.add( new ButtonInput( 'Procedural' ).setIcon( 'ti ti-infinity' ), proceduralContext );
		context.add( new ButtonInput( 'Utils' ).setIcon( 'ti ti-apps' ), utilsContext );

		this.nodesContext = context;

	}

}