/**
 * @since Wed Jan 17 2018
 * @author Charles Gouldmann - Klean
 * 
 * TODO: Rewrite the variables to `appHelper` which extends the current `AppHelper` import. Thereby moving most of the
 * logic to the settings folder, instead of splitting it. Store-helper settings might not be rewriteable with the same logic
 */

import { Injectable } from '@angular/core'
import { website } from '@k-settings/app-setup'
import { storeId, storeCode, websiteId, measuringSystem, server, mediaServer, apikey } from '@k-settings/store-helper'
import { AppHelper } from '@k-settings/app-helper'

import { isPlatformBrowser } from '@angular/common'
import { ReplaySubject } from 'rxjs'
import { makeStateKey, TransferState } from '@angular/platform-browser'
import { HttpClient } from '@angular/common/http'
import { tap } from 'rxjs/operators'

const SITEINFO = makeStateKey('siteInfoKey')

@Injectable()
export class HelperService {
	
	constructor(
		public state: TransferState,
		private _http: HttpClient
	) {

		// Isolate siteInfo in generally available observable
		this.siteInfo = this.state.get(SITEINFO, null as any)

		if(!this.siteInfo) {
			this._http.get(this.server+'feed/get/site'+this.key+'&extend'+this.GETwebsite+this.GETstore)
			.pipe(
				tap((response: any) => { this.siteDataSource.next(response.data.siteInfo) })
			)
			.subscribe((response: any) => this.state.set(SITEINFO, response.data.siteInfo as any))
		} else {
			this.siteDataSource.next(this.siteInfo)
		}

	}


	// Variables for use in project
	// -------------------------------------------

	// Scope
	website			= website

	// Server & Credentials
	server          = server
	mediaServer     = mediaServer
	
	apiKey          = apikey
	storeId         = storeId
	storeCode       = storeCode
	websiteId       = websiteId
	
	GETstore           = '&storeId=' + this.storeId
	GETwebsite         = '&websiteId=' + this.websiteId
	
	key                 = '?key=' + this.apiKey
	credentials         = this.key + this.GETstore + this.GETwebsite
	

	// General
	measurementType 	= measuringSystem;
	fallbackImageUrl    = './assets/images/image_coming_soon.svg'

	// gloster specific type filter
	types               = []

	siteDataSource = new ReplaySubject()
	siteData$ = this.siteDataSource.asObservable()


	// TODO: Change references to following vars to AppHelper instead
	siteIdentifier  = AppHelper.siteIdentifier
	category        = AppHelper.category
	categories      = AppHelper.categories

	
	siteInfo: {
		[key: string]: string | object,
		extend: {[key: string]: any}
	}

	// Functions ----

	/**
	 * Returns a credentials string with custom store prefix
	 * 
	 * @param storeIdPrefix 
	 */
	getCustomCredentials(storeIdPrefix: number) {
		return this.key + '&storeId='+storeIdPrefix+this.storeId + this.website
	}



	/**
	 * Scroll to `target` position or top if no target is given
	 * 
	 * @param target - class or id selector
	 * 
	 * TODO: add smooth / instant option to scroll
	 */
	scrollTo(target: string = '') {
		if(isPlatformBrowser) {
		
			if(typeof window !== 'undefined') {
				if(!!target) {
					if(!!document.querySelector(target)) {
						document.querySelector(target).scrollIntoView({behavior: "smooth"})
					}
					else {
						window.scrollTo(0, 0)
					}
				}
				else {
					// Just go to top
					window.scrollTo(0, 0)
				}
			}
		}
	}

	/**
	 * Check is element has given `className`
	 * @param elem 
	 * @param className 
	 */
	hasClass (elem, className) {
		return new RegExp(' ' + className + ' ').test(' ' + elem.className + ' ');
	}

	/**
	 * Add class to given element
	 * @param elem 
	 * @param className 
	 */
	addClass(elem, className) {
		if (!this.hasClass(elem, className)) {
			elem.className += ' ' + className;
		}
	}

	/**
	 * Remove class from given element
	 * @param elem 
	 * @param className 
	 */
	removeClass(elem, className) {
		let newClass = ' ' + elem.className.replace( /[\t\r\n]/g, ' ') + ' ';
		if (this.hasClass(elem, className)) {
			while (newClass.indexOf(' ' + className + ' ') >= 0 ) {
				newClass = newClass.replace(' ' + className + ' ', ' ');
			}
			elem.className = newClass.replace(/^\s+|\s+$/g, '');
		}
	}


	// TODO: Add isPlatformBrowser check

	/**
	 * Returns current documents width
	 */
	getWidth() {
		let width = 0
		if(typeof document !== 'undefined') {
			width = Math.max(
				document.body.scrollWidth,
				document.documentElement.scrollWidth,
				document.body.offsetWidth,
				document.documentElement.offsetWidth,
				document.documentElement.clientWidth
			)
		}

		return width
	}
	
	/**
	 * Returns current documents height
	 */
	getHeight() {
		let height = 0
		if(typeof document !== 'undefined') {
			height = Math.max(
				document.body.scrollHeight,
				document.documentElement.scrollHeight,
				document.body.offsetHeight,
				document.documentElement.offsetHeight,
				document.documentElement.clientHeight
			)
		}

		return height
	}


	/**
	 * Parse a radial angle to translate into degrees
	 * 
	 * @param angle 
	 */
	rad2deg(angle) {
		//   example 1: rad2deg(3.141592653589793);
		//   returns 1: 180
		return angle * (180/Math.PI); 
	}

	/**
	 * Parse a degree angle to parse into radial
	 * 
	 * @param angle 
	 */
	deg2rad(angle) {
		//   example 1: rad2deg(3.141592653589793);
		//   returns 1: 180

		return angle * (Math.PI/180);
	}


	/**
	 * Returns true if device pixel-ration is equal or greater than 2
	 * 
	 * @returns boolean
	 * @todo correct false positives from zoom
	 */
	isRetina(): boolean {
		let isRetina = false
		if(typeof window !== 'undefined') {
			isRetina = window.devicePixelRatio >= 2
		}

		return isRetina
	}


	/**
	 * Returns a boolean value depending on whether the 
	 * given `breakpoint` is active
	 * 
	 * @param breakpoint 
	 */
	currentBreakPoint(breakpoint: string) {
		let palm_small 		= 479,
		lap_start 			= 681,
		device_small 		= 870,
		desk_start 			= 960,
		desk_wide_start 	= 1200,
		ultra_wide 			= 1600,
		palm_end 			= (lap_start - 1),
		lap_end 			= (desk_start - 1),
		desk_end 			= (desk_wide_start - 1)

		let width = this.getWidth()

		let withinBreakpoint = false

		switch(breakpoint) {
			case 'palm-small':
				withinBreakpoint = width <= palm_small
				break
			case 'palm':
				withinBreakpoint = width <= palm_end
				break
			case 'lap':
				withinBreakpoint = width >= lap_start && width <= lap_end
				break
			case 'lap-and-up':
				withinBreakpoint = width >= lap_start
				break
			case 'portable':
				withinBreakpoint = width <= lap_end
				break
			case 'below-desk':
				withinBreakpoint = width <= desk_end
				break
			case 'desk':
				withinBreakpoint = width >= desk_start && width <= desk_end
				break
			case 'device-small':
				withinBreakpoint = width >= device_small && width <= desk_end
				break
			case 'desk-and-up':
				withinBreakpoint = width >= desk_start
				break
			case 'desk-wide':
				withinBreakpoint = width >= desk_wide_start
				break
			case 'ultra-wide':
				withinBreakpoint = width >= ultra_wide
				break
		}

		return withinBreakpoint
	}


	/**
	 * Filters null values out of array
	 * 
	 * @param test_array 
	 */
	filter_array(test_array) {
		let index = -1;
		const arr_length = test_array ? test_array.length : 0;
		let resIndex = -1;
		const result = [];
	
		while (++index < arr_length) {
			const value = test_array[index];
	
			if (value) {
				result[++resIndex] = value;
			}
		}
	
		return result;
	}


	/**
	 * ## LogRequestError
	 * Logs out a dataError, displaying it as intended
	 * 
	 * @todo Find a way to get the file position dynamically, rather than request it
	 * 
	 * @tutorial
 ```js
	.catch(error => {
		_helper.logRequestError(error, 'GET', '/feed/get/...', '/app-core/modules/...')
	})
```
	* 
	* @param {string} message Error message, generally comes in `response.data.errorMessage`
	* @param {string} method GET | POST | PATCH | DELETE | UPDATE
	* @param {string} request Path to request
	* @param {string} file Path to file
	* @returns {void} void return, but global `console.error`
	*/
	logRequestError(message?: any, method?: string, request?: string, file?: string): void {


// console.error(
// `Caught dataError: ${message}
// 	at method ${method}
// 	at request ${request}
// 	at ${file}
// `
// )

	}

	/**
	 * Converts binary (string) to hexadecimal
	 * 
	 * @tutorial http://locutus.io/php/bin2hex/
 ```
bin2hex('Kev')
returns '4b6576'

bin2hex(String.fromCharCode(0x00))
returns '00'
```
	* @param string 
	*/
	bin2hex (string) {

		var i
		var l
		var o = ''
		var n

		string += ''

		for (i = 0, l = string.length; i < l; i++) {
			n = string.charCodeAt(i)
			.toString(16)
			o += n.length < 2 ? '0' + n : n
		}

		return o
	}


	/**
	 * 
	 * @author Phillipe Baumann
	 * @tutorial http://locutus.io/php/hexdec/
	 * @param {string} hexString turns parsed hex string into decimal
	 */
	hexdec (hexString) {

		hexString = (hexString + '').replace(/[^a-f0-9]/gi, '')
		return parseInt(hexString, 16)
	}
	
	mapToObject(map) {

		let obj = {};
			map.forEach ((v,k) => { obj[k] = v });
			return obj;
	}

	/**
	 * @author Zia
	 * converts a hex number and return rgba css function from it
	 * Why though?
	 * 
	 * @param hex number 
	 * @param opacity 
	 */
	colorHex2Rgb(hex, opacity=50) {

		hex = hex.replace('#','')
		let red = parseInt(hex.substring(0,2), 16)
		let green = parseInt(hex.substring(2,4), 16)
		let blue = parseInt(hex.substring(4,6), 16)

		return 'rgba('+red+','+green+','+blue+','+opacity/100+')'
	}

	/**
	 * Deep clones an object in the slowest (but safest) possible way
	 * 
	 * @param obj 
	 * @returns 
	 */
	deepClone(obj): any {
		return JSON.parse(JSON.stringify(obj))
	}


	// Finds each line, determines if it has a value, and adds it if it does
	lines (arr: string[], seperator: string = ':', blacklistedValues: string[] = [], blacklistedKeys: string[] = []): string {
									
		let summary: string = ''

		// Remove upper casing from blacklist arrays
		if(blacklistedValues.length) {
			console.log('blacklistedValues', blacklistedValues)
			blacklistedValues = blacklistedValues.map((value) => (value === undefined ? '' : value).toLowerCase())
		}

		if(blacklistedKeys.length) {
			console.log('blacklistedKeys', blacklistedKeys)
			blacklistedKeys = blacklistedKeys.map((key) => (key === undefined ? '' : key).toLowerCase())
		}

		for(let line of arr) {
		
			// Isolates value for DRY
			let key = line.split(seperator)[0]
			key = key ? key.trim() : undefined

			let value = line.split(seperator)[1]
			value = value ? value.trim() : undefined

			// Checks value exists, has a length and is not a blank space, as that seems to be return from PLATO
			if(!!key && !!value && !blacklistedValues.includes(value.toLowerCase()) && !blacklistedKeys.includes(key.toLowerCase())) {
				summary += `${line}<br />`
			}
		}

		return summary
	}
}