package v6.apps.zelvak;

import java.awt.Color;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

final class ParsingContext {

	private final List<Command> commands = new LinkedList<Command>();
	
	private final Map<String, ParsingFunction> functions;
	
	public ParsingContext() {
		this(new HashMap<String, ParsingFunction>(128));
	}
	
	public ParsingContext(Map<String, ParsingFunction> functions) {
		this.functions = functions;
		functions.put("left", new RotationFunction(-1));
		functions.put("right", new RotationFunction(+1));
		functions.put("pen", new FixedArgumentsParsingFunction(1){
			@Override
			protected Command applyCheckedArguments(List<Object> arguments) throws WTFException {
				if(! (arguments.get(0) instanceof Double)){
					throw new WTFException("Unexpected argument type.");
				}
				final double arg = ((Double)arguments.get(0)).doubleValue();
				return new Command() {
					@Override
					public void execute(RunningContext context) throws RunningException {
						context.setPen(arg);
						//context.setPaint(arg > 0);
					}
				};
			}
		});
		functions.put("forward", new FixedArgumentsParsingFunction(1){
			@Override
			protected Command applyCheckedArguments(List<Object> arguments) throws WTFException {
				if(! (arguments.get(0) instanceof Double)){
					throw new WTFException("Unexpected argument type.");
				}
				final double arg = ((Double)arguments.get(0)).doubleValue();
				return new Command() {
					@Override
					public void execute(RunningContext context) throws RunningException, InterruptedException {
						final Point currentPoint = context.getPosition();
						final double angle = context.getAngle();
						final Point newPoint = new Point(
								currentPoint.getX() + arg * Math.cos(angle),
								currentPoint.getY() + arg * Math.sin(angle)
						);
						if(context.getPen() != 0){
							context.doStep();
							final double size = context.getPen();
							final double a = Math.sin(context.getAngle())*size/2;
							final double b = Math.cos(context.getAngle())*size/2;
							//System.out.println(b);
							final Point p1 = new Point(currentPoint.getX() + a, currentPoint.getY() - b);
							final Point p4 = new Point(currentPoint.getX() - a, currentPoint.getY() + b);
							final Point p2 = new Point(newPoint.getX() + a, newPoint.getY() - b);
							final Point p3 = new Point(newPoint.getX() - a, newPoint.getY() + b);
							//int width = Math.round((float)context.getPen());
							/*context.getGraphics().fillPolygon(
								new int[]{currentPoint.getXInt(), currentPoint.getXInt()+width, newPoint.getXInt()+width, newPoint.getXInt()},
								new int[]{currentPoint.getYInt(), currentPoint.getYInt(), 		newPoint.getYInt()		, newPoint.getYInt()}
								, 4);*/
							context.getGraphics().fillPolygon(
									new int[]{p1.getXInt(), p2.getXInt(), p3.getXInt(), p4.getXInt()},
									new int[]{p1.getYInt(), p2.getYInt(), p3.getYInt(), p4.getYInt()}
									, 4);
							
							/*context.getGraphics().drawLine(currentPoint.getXInt(), currentPoint.getYInt(),
									newPoint.getXInt(), newPoint.getYInt());*/
						}
						context.setPosition(newPoint);
					}
				};
			}
		});
		functions.put("color", new FixedArgumentsParsingFunction(3){
			@Override
			protected Command applyCheckedArguments(List<Object> arguments) throws WTFException {
				for(int i=0; i<3; i++){
					if(! (arguments.get(i) instanceof Double)){
						throw new WTFException("Unexpected argument type.");
					}
				}
				final float r = getColor(arguments, 0);
				//System.out.println(r);
				final float g = getColor(arguments, 1);
				final float b = getColor(arguments, 2);
				return new Command() {
					@Override
					public void execute(RunningContext context) throws RunningException {
						context.getGraphics().setColor(new Color(r, g, b));
					}
				};
			}
			
			private float getColor(List<Object> arguments, int pos) {
				return Math.min(1, Math.max(0, ((Double)arguments.get(pos)).floatValue()/255));
			}
		});
		functions.put("repeat", new FixedArgumentsParsingFunction(2, true){
			@Override
			protected Command applyCheckedArguments(List<Object> arguments) throws WTFException {
				if(! (arguments.get(0) instanceof Double)){
					throw new WTFException("Unexpected argument type.");
				}
				if(! (arguments.get(1) instanceof Application)){
					throw new WTFException("Unexpected argument type.");
				}
				final double count = ((Double)arguments.get(0)).doubleValue();
				final Application app = (Application)arguments.get(1);
				return new Command() {
					@Override
					public void execute(RunningContext context) throws RunningException, InterruptedException {
						//System.out.println("begin");
						for(int i=0; i<count; i++){
							//System.out.println(i);
							app.run(context);
						}
						//System.out.println("end");
					}
				};
			}
		});
	}

	public ParsingFunction getFunction(String name) {
		return functions.get(name);
	}

	public void addCommand(Command command) {
		commands.add(command);
	}

	public Application buildApplication() {
		return new Application(commands);
	}

	public ParsingContext makeSubContext() {
		return new ParsingContext(functions);
	}

}
