package eu.printingin3d.models.models.pokemon;

import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;

import eu.printingin3d.javascad.coords.Angles3d;
import eu.printingin3d.javascad.coords.Coords3d;
import eu.printingin3d.javascad.coords2d.Coords2d;
import eu.printingin3d.javascad.enums.Side;
import eu.printingin3d.javascad.models.Abstract3dModel;
import eu.printingin3d.javascad.models.Cube;
import eu.printingin3d.javascad.models.Cylinder;
import eu.printingin3d.javascad.models.Ring;
import eu.printingin3d.javascad.models.Sphere;
import eu.printingin3d.javascad.models2d.Polygon;
import eu.printingin3d.javascad.tranzitions.Difference;
import eu.printingin3d.javascad.tranzitions.Intersection;
import eu.printingin3d.javascad.tranzitions.Mirror;
import eu.printingin3d.javascad.tranzitions.Union;
import eu.printingin3d.javascad.utils.IModelProvider;
import eu.printingin3d.javascad.utils.ModelWithPath;
import eu.printingin3d.javascad.utils.SaveScadFiles;

public class PokeBall implements IModelProvider {
	private static final double MIN_THICKNESS = 1.0;
	
	private static final double OUTER_DIAMETER = 63.5;
	private static final double SHELL_THICKNESS = 3;
	private static final double BUTTON_THICKNESS = 5;
	private static final double BELT_WIDTH = 5;
	private static final double BELT_THICKNESS = 1.5;
	private static final double BUTTON_RADIUS = 10;
	private static final double BUTTON_GAP = 1.0;
	
	private static final double MAGNET_RADIUS = 2;
	private static final double MAGNET_DEPTH = 2;

	@Override
	public Abstract3dModel getAssembledModel() {
		Abstract3dModel top = createTop();
		return new Union(
				top, 
				createBottom()
					.align(Side.BOTTOM, top, false),
				createButton()
					.rotate(Angles3d.ROTATE_MINUS_X)
					.align(Side.FRONT, Coords3d.ZERO)
					.move(Coords3d.yOnly(getButtonHoleDistance()))
			);
	}

	@Override
	public List<ModelWithPath> getModelsAndPaths() {
		return Arrays.asList(
				new ModelWithPath(createTop(), "pokeball.top.print.scad"),
				new ModelWithPath(Mirror.mirrorZ(createBottom()), "pokeball.bottom.print.scad"),
				new ModelWithPath(createButton(), "pokeball.button.print.scad"),
				new ModelWithPath(getAssembledModel(), "pokeball.scad")
			);
	}
	
	private Abstract3dModel createTop() {
		Abstract3dModel belt = createBelt();
		double magnetDist = getMagnetDist();
		
		return new Difference(
				createBall(),
				belt,
				new Cube(100).align(Side.TOP, Coords3d.ZERO),
				createButtonHole(),
				createMagnetHole()
					.align(Side.BOTTOM, Coords3d.ZERO)
					.moves(Arrays.asList(Coords3d.xOnly(-magnetDist), Coords3d.xOnly(+magnetDist)))
			);
	}
	
	private Abstract3dModel createBottom() {
		Abstract3dModel belt = createBelt();
		double magnetDist = getMagnetDist();
		
		return new Difference(
						createBall(),
						belt,
						new Cube(100).align(Side.BOTTOM, Coords3d.ZERO),
						createButtonHole(),
						Mirror.mirrorZ(createMagnetHole())
								.align(Side.TOP, Coords3d.ZERO)
								.moves(Arrays.asList(Coords3d.xOnly(-magnetDist), Coords3d.xOnly(+magnetDist)))
						);
	}

	private Abstract3dModel createButton() {
		double r = OUTER_DIAMETER/2;
		
		double h = Math.sqrt((r-BELT_THICKNESS)*(r-BELT_THICKNESS)-BUTTON_RADIUS*BUTTON_RADIUS);
		double buttonLength = h-getButtonHoleDistance();
		
		Abstract3dModel firstLayer = new Cylinder(buttonLength, BUTTON_RADIUS-BUTTON_GAP);
		Abstract3dModel secondLayer = new Cylinder(1.5, (BUTTON_RADIUS-BUTTON_GAP)*3/4)
				.align(Side.TOP, firstLayer, false);
		return new Union(
				firstLayer,
				secondLayer,
				new Cylinder(1.5, (BUTTON_RADIUS-BUTTON_GAP)*2/4)
					.align(Side.TOP, secondLayer, false)
			);
	}

	private double getButtonHoleDistance() {
		return OUTER_DIAMETER/2-BUTTON_THICKNESS+MIN_THICKNESS;
	}
	
	private Ring createBelt() {
		return new Ring(OUTER_DIAMETER/2, 
				new Polygon(Arrays.asList(
						new Coords2d(-BELT_THICKNESS, -BELT_WIDTH/2+1),
						new Coords2d(-BELT_THICKNESS, +BELT_WIDTH/2-1),
						new Coords2d(+BELT_THICKNESS, +BELT_WIDTH/2),
						new Coords2d(+BELT_THICKNESS, -BELT_WIDTH/2)
				)));
//						new Dims2d(BELT_THICKNESS*2, BELT_WIDTH)));
//		new Square(new Dims2d(BELT_THICKNESS*2, BELT_WIDTH)));
	}
	
	private Abstract3dModel createBall() {
		double r = OUTER_DIAMETER/2;

		double magnetDist = getMagnetDist();
		
		return new Union(
			new Difference(
				new Sphere(r),
				new Sphere(r-SHELL_THICKNESS)
			),
			new Intersection(
				new Sphere(r),
				new Cylinder(r, MAGNET_RADIUS+MIN_THICKNESS)
					.moves(Arrays.asList(Coords3d.xOnly(-magnetDist), Coords3d.xOnly(+magnetDist)))
				),
			new Intersection(new Sphere(r),
				new Cylinder(SHELL_THICKNESS, Math.sqrt(r*r-(r-BUTTON_THICKNESS)*(r-BUTTON_THICKNESS))-2)
						.rotate(Angles3d.ROTATE_PLUS_X)
						.move(Coords3d.yOnly(r-BUTTON_THICKNESS+SHELL_THICKNESS/2))
				)
		);
	}

	private double getMagnetDist() {
		return OUTER_DIAMETER/2-(MAGNET_RADIUS+MIN_THICKNESS)-BELT_THICKNESS;
	}

	private Abstract3dModel createButtonHole() {
		return new Cylinder(SHELL_THICKNESS*2, BUTTON_RADIUS)
			.rotate(Angles3d.ROTATE_PLUS_X)
			.move(Coords3d.yOnly(getButtonHoleDistance()+SHELL_THICKNESS));
	}
	
	private Abstract3dModel createMagnetHole() {
		Cylinder base = new Cylinder(MAGNET_DEPTH, MAGNET_RADIUS);
		return new Union(
				base,
				new Cylinder(MAGNET_RADIUS, MAGNET_RADIUS, 0)
					.align(Side.TOP, base, false)
			);
	}
	
	public static void main(String[] args) throws IOException {
		
		new SaveScadFiles(new File("C:/temp/pokemon")).
				addModelProvider(new PokeBall()).
				saveScadFiles();
		
	}

}
