// Import the core angular services.
import { Component } from "@angular/core";
import { OnChanges } from "@angular/core";
import { SimpleChange } from "@angular/core";
import { SimpleChanges } from "@angular/core";

// Animation-oriented imports.
import { animate } from "@angular/core";
import { AnimationTransitionEvent } from "@angular/core";
import { state } from "@angular/core";
import { style } from "@angular/core";
import { transition } from "@angular/core";
import { trigger } from "@angular/core";

interface InputChanges extends SimpleChanges {
	type?: SimpleChange;
	value?: SimpleChange;
}

// We have two different elements (next value and previous value) being animated in
// unison. In order to keep both animations in sync, we'll define one timing value and
// then just reuse that in all of the animate() calls.
var valueAnimationTiming = "200ms ease-in-out";

@Component({
	moduleId: module.id,
	selector: "emoticon-button",
	inputs: [ "type", "value" ],
	animations: [
		trigger(
			"currentValue",
			[
				// Transition into view, from below.
				transition(
					"none => moving-up",
					[
						style({
							opacity: "0",
							transform: "translateY( 100% )"
						}),
						animate(
							valueAnimationTiming,
							style({
								opacity: "1",
								transform: "translateY( 0% )"
							})
						)
					]
				),
				// Transition into view, from above.
				transition(
					"none => moving-down",
					[
						style({
							opacity: "0",
							transform: "translateY( -100% )"
						}),
						animate(
							valueAnimationTiming,
							style({
								opacity: "1",
								transform: "translateY( 0% )"
							})
						)
					]
				)
			]
		),
		trigger(
			"previousValue",
			[
				// The actual DOM element for the previous value is only present during
				// the animation itself (see ngIf in template). As such, it won't be
				// transitioning from the "none" default state - it will be transitioning
				// into existence, from the "void" state.
				// --
				// Transition out of view, to above.
				transition(
					"void => moving-up",
					[
						style({
							opacity: "1",
							transform: "translateY( -100% )"
						}),
						animate(
							valueAnimationTiming,
							style({
								opacity: "0",
								transform: "translateY( -200% )"
							})
						)
					]
				),
				// Transition out of view, to below.
				transition(
					"void => moving-down",
					[
						style({
							opacity: "1",
							transform: "translateY( -100% )"
						}),
						animate(
							valueAnimationTiming,
							style({
								opacity: "0",
								transform: "translateY( 0% )"
							})
						)
					]
				)
			]
		)
	],
	styleUrls: [ "./emoticon-button.component.css" ],
	template:
	`
		<span class="emoticon emoticon--{{ type }}"></span>
		<span class="counter">

			<span
				[@currentValue]="valueState"
				(@currentValue.done)="handleAnimationDone( $event )"
				class="current-value">
				{{ value }}
			</span>

			<!--
				We only need to include the previous-value when we are animating the
				increment / decrement. That means that the previous-value is never in
				the "none" state - it goes directly from "void" to an animation state.
			-->
			<span
				*ngIf="( valueState !== 'none' )"
				[@previousValue]="valueState"
				class="previous-value">
				{{ previousValue }}
			</span>

		</span>
	`
})
export class EmoticonButtonComponent implements OnChanges {

	public previousValue: number;
	public type: string;
	public value: number;
	public valueState: string;


	// I initialize the emoticon button component.
	constructor() {

		this.previousValue = 0;
		this.type = "smile";
		this.value = 0;
		this.valueState = "none";

	}


	// ---
	// PUBLIC METHODS.
	// ---


	// I handle the animation "done" callback event.
	public handleAnimationDone( event: AnimationTransitionEvent ) : void {

		// CAUTION: If an animation transition is interrupted by a state-change, the
		// "done" callback will be fired for the interrupted transition. In that case,
		// the "toState" of the event will not match the "viewState" of the component. We
		// can use this fact to only "reset" the state when we have an expected outcome.
		if ( this.valueState !== "none" && ( this.valueState === event.toState ) ) {

			this.valueState = "none";

		}

	}


	// I get called whenever the bound inputs change (including the first binding).
	public ngOnChanges( changes: InputChanges ) : void {

		// After the value is initialized, subsequent changes to the value will be
		// classified as "moving-up" or "moving-down" actions, which will be given
		// some animation goodness.
		if ( changes.value && ! changes.value.isFirstChange() ) {

			this.previousValue = changes.value.previousValue;

			// Determine "direction" of value change for animation.
			this.valueState = ( changes.value.currentValue > changes.value.previousValue )
				? "moving-up" // Incrementing the value.
				: "moving-down" // Decrementing the value.
			;

		}

	}

}