import * as THREE from '../../build/three.module.js'
import { GLTFLoader } from '../jsm/loaders/GLTFLoader.js';
import { DRACOLoader } from '../jsm/loaders/DRACOLoader.js';
import { RGBELoader } from '../jsm/loaders/RGBELoader.js';

export class Pool {

	constructor( callback, tileSize=64, normal=true, roughness=false, pixel=false, env=true ) {

		this.sky = 'day';

		this.callback = callback;

		this.tileSize = tileSize
		this.isWithNormal = normal 
		this.isWithRoughness = roughness
		this.isPixelStyle = pixel
		this.isWithEnv = env

		this.loaderGLB = new GLTFLoader();
    	let dracoLoader = new DRACOLoader().setDecoderPath( './build/draco/' )
    	this.loaderGLB.setDRACOLoader( dracoLoader )

		this.mapPath = './assets/textures/'
		this.modelPath = './assets/models/'

		this.modelSrc = [ 'cars', 'world' ];

		this.imgSrc = ['tiles.png','town.png','building.png', 'cars.png' ];

		if( this.isWithNormal ) this.imgSrc.push( 'tiles_n.png', 'building_n.png', 'town_n.png' )
		if( this.isWithRoughness ) this.imgSrc.push( 'tiles_r.png', 'building_r.png', 'town_r.png' )
		this.imgSrc.push( 'border.jpg', 'border_a.jpg' )

		this.imgs = [];
		this.num = 0;

		this.tiles = {
			normal:[],
			roughness:[],
			texture:[],
		}

		this.color = {
			ground:'#c68564',
			normal:'#8080ff',
			snow:'#e6f0ff',
			white:'#ffffff',
			lightGrey:'#CCCCCC',
			metal:'#AAAAAA',
			sky:'#8397ac',
		}

		this.textures = {}
		this.geos = {}

		if (this.isWithEnv) this.loadEnvmap()
		else this.loadImages()

	}

	displayMessage( str ){

		if( hub ) hub.message( str )

	}
 

	//----------------------------------- ENVMAP

	loadEnvmap() {

		this.displayMessage( 'Loading envmap ...' )

		new RGBELoader().load(
			this.mapPath + this.sky + '.hdr',
			function ( texture ) {

				this.env = texture;
				this.loadImages();

			}.bind(this),
			undefined,
			function ( err ) {
				console.warn('[3dcity] Failed to load envmap, continuing without it:', err);
				this.env = null;
				this.loadImages();
			}.bind(this)
		);

	}


	//----------------------------------- TEXTURES

	loadImages() {

		this.displayMessage( 'Loading images ...' )

		let n = this.num;
		let url = this.imgSrc[n]
		let name = url.substring( url.lastIndexOf('/')+1, url.lastIndexOf('.') );


    	this.imgs[name] = new Image();
    	this.imgs[name].onload = function(){ 
    		this.num++;
    		if( this.num === this.imgSrc.length ) this.defineCanvas();
    		else this.loadImages();
    	}.bind(this);
    	this.imgs[name].onerror = function(err){
    		console.warn('[3dcity] Failed to load image:', url, err);
    		this.num++;
    		if( this.num === this.imgSrc.length ) this.defineCanvas();
    		else this.loadImages();
    	}.bind(this);
        this.imgs[name].src = this.mapPath + url; 

	}



	defineCanvas() {

		this.num = 0;

		this.canvas = {
			town: this.makeCanvas( 'town' ),
			building: this.makeCanvas( 'building' ),
			tiles: this.makeCanvas( 'tiles', true ),
		}

		if( this.isWithNormal ) this.canvas[ 'tiles_n' ] = this.makeCanvas( 'tiles_n', true )
		if( this.isWithRoughness ) {
			this.canvas[ 'tiles_r' ] = this.makeCanvas( 'tiles_r', true )
			this.canvas[ 'town_r' ] = this.makeCanvas( 'town_r' )
			this.canvas[ 'building_r' ] = this.makeCanvas( 'building_r' )
		}

	    this.drawCanvas()

	    this.makeCarColor()

	}

	makeCanvas( name, resize ) {

		let r = 1;
		if (resize) {
			if (this.tileSize === 32) r = 0.5;
			else if (this.tileSize === 16) r = 0.25;
		}

		let img = this.imgs[name];
		let c = document.createElement("canvas")
		c.width = img.width*r
		c.height = img.height*r
		return c

	}

	drawCanvas() {

		let c, ctx;

		// TODO add color effect on canvas

		for( let name in this.canvas ){

			c = this.canvas[name];

			ctx = c.getContext('2d');
			ctx.clearRect ( 0 , 0, c.width, c.height );
			if( name === 'tiles' || name === 'town' || name === 'building'){
				ctx.fillStyle = this.color.ground;
				ctx.fillRect( 0, 0, c.width, c.height )
			}
			if( name === 'tiles_n' ){
				ctx.fillStyle = this.color.normal;
				ctx.fillRect( 0, 0, c.width, c.height )
			}
			if( name === 'tiles_r' || name === 'town_r' || name === 'building_r'){
				ctx.fillStyle = this.color.lightGrey;
				ctx.fillRect( 0, 0, c.width, c.height )
			}

			 ctx.drawImage( this.imgs[ name ], 0, 0, c.width, c.height );
		
		}

		this.defineTextures()

	}

	//this.tint( this.townCanvas, this.imgs[1], this.imgs[4] );
	//this.tint( this.buildingCanvas, this.imgs[2], this.imgs[3] );


	defineTextures() {

		this.makePixelData( 'tiles' )

		this.textures['town'] = new THREE.Texture( this.canvas.town );
		this.filterTexture( this.textures['town'], { flip:false } )

        this.textures['building'] = new THREE.Texture( this.canvas.building );
        this.filterTexture( this.textures['building'], { flip:false } )

        if( this.isWithNormal ){

        	this.makePixelData( 'tiles_n' )

        	this.textures['town_n'] = new THREE.Texture( this.imgs['town_n'] );
			this.filterTexture( this.textures['town_n'], { flip:false, normal:true } )

	        this.textures['building_n'] = new THREE.Texture( this.imgs['building_n'] );
	        this.filterTexture( this.textures['building_n'], { flip:false, normal:true } )

        }

        if( this.isWithRoughness ){

        	this.makePixelData( 'tiles_r' )

        	this.textures['town_r'] = new THREE.Texture( this.canvas.town_r );
			this.filterTexture( this.textures['town_r'], { flip:false, normal:true } )

	        this.textures['building_r'] = new THREE.Texture( this.canvas.building_r );
	        this.filterTexture( this.textures['building_r'], { flip:false, normal:true } )

        }

        this.textures['border'] = new THREE.Texture( this.imgs['border'] );
		this.filterTexture( this.textures['border'], { flip:false } )

		this.textures['border_a'] = new THREE.Texture( this.imgs['border_a'] );
		this.filterTexture( this.textures['border_a'], { flip:false } )

        this.loadModel()

	}

	filterTexture ( texture, o = {} ){

    	if( !o.normal ) texture.encoding = THREE.sRGBEncoding
    	if( o.flip !== undefined ) texture.flipY = o.flip
    	if( o.midmap !== undefined ) texture.generateMipmaps = o.midmap;
    	if( o.alpha !== undefined ) texture.premultiplyAlpha = o.alpha;

    	if( this.isPixelStyle ){
    		texture.magFilter = THREE.NearestFilter;
    	    texture.minFilter = THREE.LinearMipMapLinearFilter;
    	} else {
    		texture.magFilter = THREE.LinearFilter;
    	    texture.minFilter = THREE.LinearMipmapLinearFilter;
    	}
    	
    	//texture.anisotropy = this.anisotropy;
    	texture.needsUpdate = true;

    }


    makePixelData( name ) {

		let ctx = this.canvas[ name ].getContext('2d')
		let pix = this.tileSize, x, y

		for ( let i = 0; i < 240; i++ ){

			x = ( i % 32 ) * pix;
			y = Math.floor( i / 32 ) * pix;
			let data = ctx.getImageData(x, y, pix, pix).data;
			if ( name === 'tiles_n' ) this.tiles.normal[i] = new THREE.DataTexture( data, pix, pix );
			else if ( name === 'tiles_r' ) this.tiles.roughness[i] = new THREE.DataTexture( data, pix, pix );
			else this.tiles.texture[i] = new THREE.DataTexture( data, pix, pix );

		}

	}


	tint( canvas, image, supImage ) {

		let data, i, n;
		let pixels = canvas.width*canvas.height;
	    let ctx = canvas.getContext('2d');
	    
	    // draw windows
	    let topData = null;
	    let newImg = null;
	    if(supImage && this.dayTime!==0 && this.dayTime!==1){
	    	ctx.clearRect ( 0 , 0 , canvas.width, canvas.height );
	        ctx.drawImage(supImage, 0, 0);
	        topData = ctx.getImageData(0, 0, canvas.width, canvas.height);
	        data = topData.data;
	        i = pixels;
	        while(i--){
	        	n = i<<2;
	        	if(data[n+3] !== 0){
	        		if(data[n+0]==0 && data[n+1]==0 && data[n+2]==0){// black
	        		    data[n+3]=60;
	        		}
	        		if(data[n+1]==0){
	        		//if(data[n+0]==255 && data[n+1]==0 && data[n+2]==0){// red
	        			if(this.dayTime==3) data[n+1]=255;
	        			if(this.dayTime==2) {data[n+0]=0; data[n+3]=60;}
	        		}

	        	}
	        }
	        ctx.putImageData(topData, 0, 0);
	        newImg = document.createElement('img');
	        newImg.src = canvas.toDataURL("image/png");
	    }

	    if(image){
	    	ctx.clearRect ( 0 , 0 , canvas.width, canvas.height );
	        ctx.drawImage(image, 0, 0);
	    } else {
	    	ctx.drawImage(this.skyCanvasBasic, 0, 0);
	    }

	    if( this.dayTime!==0 ){
		    let imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
		    data = imageData.data;
		    i = pixels;
		    let c = this.tcolor;
		    while(i--){
		    	n = i<<2;//i*4;
		    	data[n+0] = data[n+0] * (1-c.a) + (c.r*c.a);
			    data[n+1] = data[n+1] * (1-c.a) + (c.g*c.a);
			    data[n+2] = data[n+2] * (1-c.a) + (c.b*c.a);
		    }
		    ctx.putImageData(imageData, 0, 0);
		    if(newImg){
		    	ctx.drawImage(newImg, 0, 0);
		    }
		}

	}

	//----------------------------------- TITLE

	rand ( low, high ) { return low + Math.random() * ( high - low ); }

	makeTitleTexture ( n = 0 ) {

		let color = [ this.color.metal, '#fff' ]
		if(n===1) color = [ '#333333', '#999999' ]
		if(n===2) color = [ '#000', '#999999' ]

	    let s = 0.25

		let c = document.createElement( 'canvas' );
		c.width = c.height = 1024*s;
		let ctx = c.getContext('2d');

		ctx.beginPath();
		ctx.fillStyle = color[0];
		ctx.rect(0, 0, 1024*s, 1024*s);
		ctx.fill();

		let i = 8, r1, r2
		while(i--){
			r1 = this.rand( 150, 255 )
			r2 = this.rand( r1-60, r1-20 )
			ctx.beginPath();
			ctx.fillStyle = n!==1 ?  'rgb('+r1+','+r1+','+r2+')': color[1];
			ctx.rect( i*146*s, 0, 146*s, 200*s);
			ctx.fill();
		}

		let t = new THREE.Texture( c )
		this.filterTexture( t, { flip:false } )
		return t;

	}




	//----------------------------------- CARS

	makeCarColor () {

		let c = document.createElement( 'canvas' );
		c.width = c.height = 1024;
		let ctx = c.getContext('2d');
		let i, n=0, j=0, k = 3;

		while(k--){

			ctx.clearRect ( 0 , 0, c.width, c.height );

			for( i=0; i<16; i++ ){
				ctx.beginPath();
				if(i!==11 && i!==15) ctx.fillStyle = this.carColor();
				ctx.rect(n*256, j*256, 256, 256);
				ctx.fill();
				n++
				if(n==4){ n=0; j++; }
			}

			ctx.drawImage( this.imgs.cars, 0, 0 );
			let name = 'cars_' + k
			this.textures[name] = new THREE.Texture( c );
	        this.filterTexture( this.textures[name], { flip:false } )

		}

	}

	carColor () {

		let carcolors = [
		[0xFFFFFF, 0xD0D1D3, 0XEFEFEF, 0xEEEEEE],//white
		[0x252122, 0x302A2B, 0x27362B, 0x2F312B],//black
		[0x8D9495, 0xC1C0BC, 0xCED4D4, 0xBEC4C4],//silver
		[0x939599, 0x424242, 0x5A5A5A, 0x747675],//gray
		[0xC44920, 0xFF4421, 0x600309, 0xD9141E],//red
		[0x4AD1FB, 0x275A63, 0x118DDC, 0x2994A6],//blue
		[0xA67936, 0x874921, 0xD7A56B, 0x550007],//brown
		[0x5FF12C, 0x188047, 0x8DAE29, 0x1AB619],//green
		[0xFFF10A, 0xFFFFBD, 0xFCFADF, 0xFFBD0A],//yellow/gold
		[0xB92968, 0x5C1A4F, 0x001255, 0xFFB7E7]//other
		];

		let l = this.randInt(0,9), n = this.randInt(0,3);
		let base = carcolors[l][n];
	    let resl = base.toString(16);
	    if(resl.length<6) resl = '#0'+resl;
	    else resl = '#'+resl;
		return resl;

	}

	randInt( low, high ) { return low + Math.floor( Math.random() * ( high - low + 1 ) ); }

	tile( type, id ) {
		return this.tiles[type][id];
	}

	texture( name ) {
		return this.textures[name];
	}


	//----------------------------------- 3D MODEL

	loadModel() {

		this.displayMessage( 'Loading 3d model ...' )

		let n = this.num;
		let name = this.modelSrc[n]

	    this.loaderGLB.load( this.modelPath + name + '.glb', function ( gltf ) {

	    	let o = {}, b1, b2, t;
	    	gltf.scene.traverse( function ( node ) {
	    		if( node.name === 'title' ) t = node;
	    		if( node.name === 'border' ) b1 = node;
	    		if( node.name === 'border_min' ) b2 = node;
				if( node.isMesh && !o[node.name] ) o[node.name] = node.geometry;
			})
			if(b1) this.border = b1;
			if(b2) this.border_min = b2;
			if(t) this.title = t;
			this.defineGeometry( o, name )

	    	this.num++;
			if( this.num === this.modelSrc.length ){ 
				this.displayMessage( '...' )
				this.callback()
			} else {
				this.loadModel()
			}

	    }.bind(this))

	}

	defineGeometry ( o, name ){

		let g, n;

		switch( name ){
    		case 'cars':

    		    g = { cars:[] }
    		    for( let c in o ){
    		    	n = Number( c.substring(4) )
    		    	g.cars[n] = o[c]
    		    }

    		break;
    		case 'world':

	    		g = {
					town:[
						null, null, null, null,
						o.police, o.park_1, o.park_2, o.fire,
						o.coal, o.nuclear, o.port, o.stadium, o.airport
					],
					tree:[
					    o.ttt3, o.ttt3, o.ttt4, o.ttt4,
					    o.ttt0, o.ttt1, o.ttt2, o.ttt5
					],
					sprite:[
					   o.train, o.elico.clone(), o.plane.clone()
					],

					residential:[],
					commercial:[],
					industrial:[],
					house:[]
				}

				// BASIC 
				let i = 9;
				while(i--) g.industrial[i] = o['i_0'+i]
				i = 19;
				while(i--) g.residential[i] = i<10 ? o['r_0'+i] : o['r_'+i]
				i = 21;
				while(i--) g.commercial[i] = i<10 ? o['c_0'+i] : o['c_'+i]
				i = 12;
				while(i--) g.house[i] = i<10 ? o['rh_0'+i] : o['rh_'+i]

    		break;
    	}

    	// ADD TO GEOS POOL 
		this.geos = { ...this.geos, ...g }

	}

	geo ( type, id ){
		return this.geos[type][id] || null;
	}


}
