importPackage(java.awt);
importPackage(java.awt.geom);
importPackage(java.nio);

include(Resources.idRelative("mtr:lib/harrys_lib.js"));
include("kt8display.js");
include("mtr:number/tatranumber.js");

var rawModels = ModelManager.loadPartedRawModel(Resources.manager(), Resources.idRelative("mtr:kt8d5_scripted/kt8d5n.obj"), null);
var rawModelsM = ModelManager.loadPartedRawModel(Resources.manager(), Resources.idRelative("mtr:kt8d5_scripted/kt8d5m.obj"), null);

var models = uploadPartedModelsKT8(rawModels);
var models2 = uploadPartedModelsKT8(rawModelsM);
var bog = 3.8;

function create(ctx, state, train) {

	if (train.trainCars < 3) {
		print("trying to create Tatra KT8D5 train with only " + train.trainCars + " cars! The train is invisible");
		return;
	} else if (train.trainCars > 3) {
		print("trying to create Tatra KT8D5 train with " + train.trainCars + " cars! Tatra KT8D5 has only 3, other cars will be invisible");
		return;
	}
	displCreate(ctx, state, train);
	state.wheelAngle = 0.0; // Градус поворота колеса радиусом 1 метр. Градус колеса радиуса x, очевидно, будет в два 1/x раз больше (или меньше)
	state.angleSec = [1.57, 1.57];
	state.angleFirst = [1.57, 1.57];
	state.pantAngle = train.isReversed() ? 0.0 : 1.0;

	numCreate(train, state);
	initWipers(state);
}

function dispose(ctx, state, train) {
	if (train.trainCars < 3) return;
	displDispose(ctx, state, train);
}

function render(ctx, state, train) {
	if (train.trainCars < 3) return;


	const wheelRadius = 0.33;

	var matrices = new Matrices(); 
	let door = train.doorValue();

	let wheelAngleCoef = state.wheelAngle / wheelRadius; // поворот колеса радиуса wheelRadius

	updateWheelAngle(train, state);
	updatePantAngle(train, state);
	displRenderLogic(ctx, state, train);
	updateWipers(train, state);

	
	
	// rendering first
	let i = 0;
	renderHead(matrices, ctx, train, state, i, door, wheelAngleCoef);

	// rendering second
	i = 1;
	renderMiddle(matrices, ctx, train, state, i, door);

	// rendering third
	i = 2;
	matrices.pushPose();
	matrices.rotate(0, 1, 0, Math.PI);
	renderHead(matrices, ctx, train, state, i, door, -wheelAngleCoef);
	matrices.popPose();

}


function renderMiddle(matrices, ctx, train, state, i, door) {	
	renderDoorMiddle(matrices, ctx, train, i, door);

	ctx.drawCarModel(models2["ext2"], i, matrices);
	ctx.drawCarModel(models2["int2"], i, matrices); 
}


function renderDoorMiddle(matrices, ctx, train, i, doorCoef) {
	let boolDoorL = train.doorLeftOpen[i] ;
	let doorL = 0;

	let boolDoorR = train.doorRightOpen[i] ;
	let doorR = 0;

	doorR = boolDoorR ? doorCoef : 0;
	doorL = boolDoorL ? doorCoef : 0;
	renderDoorSlide(-0.05, 0.0, 0.0, 0.7, models2["rf"], models2["intrf"], doorR, ctx, matrices, i);
	renderDoorSlide(-0.05, 0.0, 0.0, -0.7, models2["rb"], models2["intrb"], doorR, ctx, matrices, i);

	renderDoorSlide(0.05, 0.0, 0.0, 0.7, models2["lf"], models2["intlf"], doorL, ctx, matrices, i);
	renderDoorSlide(0.05, 0.0, 0.0, -0.7, models2["lb"], models2["intlb"], doorL, ctx, matrices, i);
}



function renderHead(matrices, ctx, train, state, i, doorCoef, wheelAngleCoef) {
	ctx.drawCarModel(models["ext"], i, matrices); // отрисовка
	ctx.drawCarModel(models["int"], i, matrices); 


	//ctx.drawCarModel(models["bogie2"], i, matrices);
	ctx.drawCarModel(models["light_off"], i, matrices);

	let rotates = calculateBogieRot(train, state, i)

	renderBogie1(matrices, ctx, train, state, i, wheelAngleCoef, rotates[0])
	renderBogie2(matrices, ctx, train, state, i, wheelAngleCoef, rotates[1])


	renderPant(ctx, train, state, i, matrices);

	renderDoor(ctx, train, state, matrices, i, doorCoef);

	renderLights(ctx, train, state, matrices, i);

	displRender(ctx, state, i, matrices);

	renderNums(ctx, train, state, matrices, i);

	renderWipers(ctx, train, state, matrices, i);


}

function renderWipers(ctx, train, state, matrices, i) {
	let coefWipers = 0.0;
	if (i == 2) {
		coefWipers = (state.wipeReverseMark) ? state.wipeCoef : 0.0;
	} 
	if (i == 0) {
		coefWipers = (!state.wipeReverseMark) ? state.wipeCoef : 0.0;
	}


	matrices.pushPose();
	matrices.translate(0.3763, 0.8560, 7.675);
	matrices.rotate(0, 0.1736, 0.9848, 0.91 * wipFunc(coefWipers));
	matrices.translate(-0.3763, -0.8560, -7.675);
	ctx.drawCarModel(models["wipr"], i, matrices)
	{
		matrices.pushPose();
		matrices.translate(0.6152, 1.346, 7.572);
		matrices.rotate(0, 0.1736, 0.9848, -0.91 * wipFunc(coefWipers));
		matrices.translate(-0.6152, -1.346, -7.572);
		ctx.drawCarModel(models["wipr2"], i, matrices)
		matrices.popPose();
	}
	matrices.popPose();

	matrices.pushPose();
	matrices.translate(-0.4061, 0.8560, 7.675);
	matrices.rotate(0, 0.1736, 0.9848, 0.91 * wipFunc(coefWipers));
	matrices.translate(0.4061, -0.8560, -7.675);
	ctx.drawCarModel(models["wipl"], i, matrices)
	{
		matrices.pushPose();
		matrices.translate(-0.1677, 1.346, 7.572);
		matrices.rotate(0, 0.1736, 0.9848, -0.91 * wipFunc(coefWipers) + 0.05);
		matrices.translate(0.1677, -1.346, -7.572);
		ctx.drawCarModel(models["wipl2"], i, matrices)
		matrices.popPose();
	}
	matrices.popPose();
	// 0.3763 0.8560 7.675
}

function renderNums(ctx, train, state, matrices, i) {
	const offset = 0.13
	matrices.pushPose();
	matrices.translate(0, 0.4, 7.67);
	matrices.rotate(1, 0, 0, Math.PI / 18)
	numRender(ctx, train, state, matrices, offset, i);
	matrices.popPose();

	matrices.pushPose();
	matrices.translate(1.25, 0.5, 2.0);
	matrices.rotate(0, 1, 0, Math.PI / 2)
	numRender(ctx, train, state, matrices, offset, i);
	matrices.popPose();

	matrices.pushPose();
	matrices.translate(-1.27, 0.5, 2.0);
	matrices.rotate(0, 1, 0, -Math.PI / 2)
	numRender(ctx, train, state, matrices, offset, i);
	matrices.popPose();
}

function strvec(vec) {
	return vec.x() + " " + vec.y() + " " + vec.z();
}

function calculateBogieRot(train, state, i) {

		let ox = new Vector3f(1.0, 0, 0);

		let lasCarYaw = (train.lastCarRotation[i].y())

		ox.rotY(lasCarYaw)

		let stateIndex = i == 0 ? 0 : 1;
		let localFirstBog = new Vector3f(0.0, 0.0, i == 0 ? bog : -bog);
		let localSecBog = new Vector3f(0.0, 0.0, i == 0 ? -bog : bog);

		

		localFirstBog.rotY(lasCarYaw);
		localSecBog.rotY(lasCarYaw);

		let trainCurPos = train.lastCarPosition[i];		

		let firstBogPos = trainCurPos.copy();
		firstBogPos.add(localFirstBog);

		let secBogPos = trainCurPos.copy();
		secBogPos.add(localSecBog);
		let oldFirstBogPos = state.oldFirstBogPos === undefined || state.oldFirstBogPos[stateIndex] === undefined ? 
														firstBogPos.copy() : state.oldFirstBogPos[stateIndex];

		let oldSecBogPos = state.oldSecBogPos === undefined || state.oldSecBogPos[stateIndex] === undefined ?
														secBogPos.copy() : state.oldSecBogPos[stateIndex];

		if (state.oldSecBogPos === undefined || state.oldFirstBogPos === undefined) {
			state.oldFirstBogPos = [];
			state.oldSecBogPos = [];
		}

		if (state.angleFirst === undefined || state.angleSec === undefined) {
			state.angleFirst = [];
			state.angleSec = [];
		}



		state.oldFirstBogPos[stateIndex] = firstBogPos.copy();
		state.oldSecBogPos[stateIndex] = secBogPos.copy();

		let angleFirst = Math.PI / 2;
		let angleSec = Math.PI / 2;

		if (isVecEquals(firstBogPos, oldFirstBogPos) || isVecEquals(secBogPos, oldSecBogPos)) {
			if (state.angleFirst !== undefined) {
				angleFirst = state.angleFirst[stateIndex];
			}
			if (state.angleSec !== undefined) {
				angleSec = state.angleSec[stateIndex];
			}
		} else {
			firstBogPos.sub(oldFirstBogPos); 
			secBogPos.sub(oldSecBogPos);

			let cosOxFirst = cos(ox, firstBogPos);
			let cosOxSec = cos(ox, secBogPos);

			angleFirst = Math.acos(cosOxFirst);
			angleSec = Math.acos(cosOxSec);

			state.angleFirst[stateIndex] = angleFirst;
			state.angleSec[stateIndex] = angleSec;
		}

		angleFirst = Math.PI / 2 - angleFirst;
		angleSec = Math.PI / 2 - angleSec;

	return train.isReversed() ? [-angleFirst, -angleSec] : [angleFirst, angleSec];
}

function isVecEqualsFloat(vec1, x, y, z) {
	return vec1.x() == x && vec1.y() == y && vec1.z() == z;
}

function isVecEquals(vec1, vec2) {
	return vec1.x() == vec2.x() && vec1.y() == vec2.y() && vec1.z() == vec2.z();
}

function cos(vec1, vec2) {
	return dot(vec1, vec2) / (len(vec1) * len(vec2));

}

function dot(vec1, vec2) {
	return vec1.x() * vec2.x() + vec1.y() * vec2.y() + vec1.z() * vec2.z();
}

function len(vec1) {
	return Math.sqrt(vec1.x() * vec1.x() + vec1.y() * vec1.y() + vec1.z() * vec1.z()); 
}

function renderBogie1(matrices, ctx, train, state, i, wheelAngleCoef, rot) {
	matrices.pushPose();
	matrices.translate(0.0, 0.0, bog);
	matrices.rotate(0.0, 1.0, 0.0, rot);
	matrices.translate(0.0, 0.0, -bog);
	ctx.drawCarModel(models["bogie1"], i, matrices);
	renderWheel(ctx, matrices, models["wh1"], wheelAngleCoef+1, i, -0.4408, 2.908); 
	renderWheel(ctx, matrices, models["wh2"], wheelAngleCoef, i, -0.4408, 4.736); 

	matrices.popPose();
}

function renderBogie2(matrices, ctx, train, state, i, wheelAngleCoef, rot) {
	matrices.pushPose();
	matrices.translate(0.0, 0.0, -bog);
	matrices.rotate(0.0, 1.0, 0.0, rot);
	matrices.translate(0.0, 0.0, bog);
	ctx.drawCarModel(models["bogie2"], i, matrices);

	renderWheel(ctx, matrices, models["wh4"], wheelAngleCoef+1, i, -0.4408, -2.968); 
	renderWheel(ctx, matrices, models["wh3"], wheelAngleCoef, i, -0.4408, -4.824);
	matrices.popPose();
}



function renderLights(ctx, train, state,matrices,  i){
	let shouldRender = i == 2 && train.isReversed() || i == 0 && !train.isReversed();
	const rotateType = checkRotate(train, state, train.isReversed());

	if (shouldRender) {
		
		ctx.drawCarModel(models["front_light_on"], i, matrices);
	} else {
		ctx.drawCarModel(models["back_light_on"], i, matrices);
	}


	if (rotateType != 0) {
			ctx.drawCarModel(models["r_light_on"], i, matrices);
			ctx.drawCarModel(models["l_light_on"], i, matrices);


	}
}

function checkRotate(train, state, isReversed) {
	let headIndex = isReversed ? 2 : 0;
	let oldrotate = state.oldRotate === undefined ? train.lastCarRotation[headIndex].y() : state.oldRotate;
  let delta =  train.lastCarRotation[headIndex].y() - oldrotate
	
	
	state.oldRotate = train.lastCarRotation[headIndex].y();
	
	return 0;
}

function renderDoor(ctx, train, state, matrices, i, doorCoef) {
	let boolDoorL = train.doorLeftOpen[i] ;
	let doorL = 0;

	let boolDoorR = train.doorRightOpen[i] ;
	let doorR = 0;

	if (i != 2) {
		doorL = boolDoorL ? doorCoef : 0;
		 doorR = boolDoorR ? doorCoef : 0;
	} else {
		doorL = boolDoorR ? doorCoef : 0;
		doorR = boolDoorL ? doorCoef : 0;
	}

	renderDoorSlide(-0.05, 0.0, 0.0, 0.55, models["rf1"], models["int_rf1"], doorR, ctx, matrices, i);
	renderDoorSlide(-0.05, 0.0, 0.0, -0.55, models["rf2"], models["int_rf2"], doorR, ctx, matrices, i);

	renderDoorSlide(-0.05, 0.0, 0.0, 0.72, models["rb1"], models["int_rb1"], doorR, ctx, matrices, i);
	renderDoorSlide(-0.05, 0.0, 0.0, -0.72, models["rb2"], models["int_rb2"], doorR, ctx, matrices, i);

	renderDoorSlide(0.05, 0.0, 0.0, 0.55, models["lf1"], models["int_lf1"], doorL, ctx, matrices, i);
	renderDoorSlide(0.05, 0.0, 0.0, -0.55, models["lf2"], models["int_lf2"], doorL, ctx, matrices, i);

	renderDoorSlide(0.05, 0.0, 0.0, 0.72, models["lb1"], models["int_lb1"], doorL, ctx, matrices, i);
	renderDoorSlide(0.05, 0.0, 0.0, -0.72, models["lb2"], models["int_lb2"], doorL, ctx, matrices, i);
}

function renderPant(ctx, train, state, i, matrices){

	let pantAngle = i == 2 ? state.pantAngle : 1 - state.pantAngle;
	//let pantAngle = state.pantAngle;

	let r1 = 46 * pantAngle;
	let r2 = -82 * pantAngle;
	let r3 = 36 * pantAngle;

	let r = 42 * pantAngle;

	let y = 2.570;
	let z = 3.813;

	let y1 = 2.567;
	let z1 = 4.105;

	let y2 = 3.830;
	let z2 = 5.027;

	let y3 = 4.867; 
	let z3 = 3.656;

	matrices.pushPose(); 
	matrices.translate(0.0, y, z);
	matrices.rotate(1.0, 0.0, 0.0, r / 180 * 3.14);
	matrices.translate(0.0, -y, -z);
	ctx.drawCarModel(models["pant1"], i, matrices);
	{

	}
	matrices.popPose();

	matrices.pushPose(); 
	matrices.translate(0.0, y1, z1);
	matrices.rotate(1.0, 0.0, 0.0, r1 / 180 * 3.14);
	matrices.translate(0.0, -y1, -z1);
	ctx.drawCarModel(models["pant11"], i, matrices);
	{
		matrices.pushPose(); 
		matrices.translate(0.0, y2, z2);
		matrices.rotate(1.0, 0.0, 0.0, r2 / 180 * 3.14);
		matrices.translate(0.0, -y2, -z2);
		ctx.drawCarModel(models["pant2"], i, matrices);
		{
			matrices.pushPose(); 
			matrices.translate(0.0, y3, z3);
			matrices.rotate(1.0, 0.0, 0.0, r3 / 180 * 3.14);
			matrices.translate(0.0, -y3, -z3);
			ctx.drawCarModel(models["pant3"], i, matrices);
			{
		
			}
			matrices.popPose();
		}
		matrices.popPose();
	}
	matrices.popPose();
	
}

function updatePantAngle(train, state){
	const speed = 1;
	let delta = Timing.delta();
	if (!train.isReversed() && state.pantAngle < 1) {
		state.pantAngle += speed * delta;
	} else if (train.isReversed() && state.pantAngle > 0) {
		state.pantAngle -= speed * delta;
	}
	state.pantAngle = clamp(state.pantAngle, 0, 1);
}

function clamp(a, min, max) {
	return Math.min(Math.max(a, min), max);
}

function getSplitDoorCoef(value, delta) {
	let r1 = 0;
	let r2 = 0;
	if (value < delta) {
		r1 = value / delta;
		r2 = 0;
	} else {
		r1 = 1;
		r2 = (value - delta) / (1 - delta);
	}

	return [r1, r2];
}

function renderDoorSlide(x1, z1, x2, z2, part, partInt, value, ctx, matrices, i) {
	let coefs = getSplitDoorCoef(value, 0.2);
	let r1 = coefs[0];
	let r2 = coefs[1];

	matrices.pushPose();
	matrices.translate(x1*r1 + x2*r2, 0.0, z1*r1 + z2*r2);
	ctx.drawCarModel(part, i, matrices); 
	if (partInt != null) ctx.drawCarModel(partInt, i, matrices); 
	matrices.popPose();

}


function updateWheelAngle(train, state) { 
	//if (!train.shouldRenderDetail()) return 0;
	let speedMps = 20 * train.speed();
	let delta = Timing.delta();
	if (train.isReversed()) {
		state.wheelAngle -= speedMps * delta; 
	} else {
		state.wheelAngle += speedMps * delta; 
	}
	
	if (state.wheelAngle > 1000) state["wheelAngle"] -= 1000;
	if (state.wheelAngle < 0) state["wheelAngle"] += 1000;
}

function uploadPartedModelsKT8(rawModels) { 
  result = {};
  for (it = rawModels.entrySet().iterator(); it.hasNext(); ) {
    entry = it.next();

    let jsStringKey = "" + entry.getKey(); // строка в жс строку
    //print("registering " + jsStringKey + ". .")
    if (jsStringKey.includes("int")) entry.getValue().setAllRenderType("interior"); //включение интерьера
    if (jsStringKey.includes("on")) entry.getValue().setAllRenderType("light"); //включение света

    entry.getValue().applyUVMirror(false, true);
    result[jsStringKey] = ModelManager.uploadVertArrays(entry.getValue());
  }
  return result;
}

function renderWheel(ctx, matrices, part, angle, i, y, z) {
	matrices.pushPose(); 
	matrices.translate(0.0, y, z);
	matrices.rotate(1.0, 0.0, 0.0, angle);
	matrices.translate(0.0, -y, -z);
	ctx.drawCarModel(part, i, matrices);
	
	matrices.popPose();
}
