var default_colors = {
        U: "#ffffff",
        D: "#ffff00",
        F: "#008800",
        B: "#0000ff",
        R: "#ff0000",
        L: "#ff8800",
        blank: "#555555",
        blank2: "#333333",
        plastic: "#000000",
        good_O: "#69aa69",
        bg: "#555555" };

function Sticker(options) {
    var self = this;

    if (options === undefined) { options = {} }
    if (options.side === undefined) { options.side = "blank" }
    if (options.shape === undefined) { options.shape = "square" }
    if (options.width === undefined) { options.width = 42 }

    if (options.side_to_color === undefined) {
        options.side_to_color = function (color) {
            return default_colors[color] } }

    if (options.color === undefined) {
        options.color = options.side_to_color(options.side) }

    if (options.stroke === undefined) { options.stroke = options.color }
    if (options.strokeWidth === undefined) { options.strokeWidth = 0 }

    if (options.points === undefined) {
        options.points = options.shape == "UD" ? points_UD(options.width) :
                         options.shape == "FB" ? points_FB(options.width) :
                         options.shape == "RL" ? points_RL(options.width) :
                         points_square(options.width) }

    var polygon = new Polygon(options);

    self.options     = options_fn;
    self.color       = polygon.color;
    self.points      = polygon.points;
    self.stroke      = polygon.stroke;
    self.strokeWidth = polygon.strokeWidth;
    self.img         = polygon.img;
    self.side        = function (arg) {
        return arg === undefined ?
               options_fn().side :
               options_fn({ side: arg, stroke: options.side_to_color(arg) }) };

    return self;

    function options_fn(arg) {
        if (arg === undefined) { return options }
        for (var key in arg) { options[key] = arg[key] }
        options.color = options.side_to_color(options.side);
        polygon.options(options);
        return self } }

function d2px(d) { return String(Math.round(d)) + "px" }

/* TODO: nice buttons */
function make_button(label, fn) {
    return $('<a href="#"><span>' + label + '</span></a>')
           .bind("click", function () { fn(); return false }) }

function CubeDisplay(width, distance) {
    if (width === undefined) {
        width = 160 }
    if (distance === undefined) {
        distance = 1 }

    var padding = 0.05;
    var self = this;

    var colors = { U: default_colors.U
                 , D: default_colors.D
                 , F: default_colors.F
                 , B: default_colors.B
                 , R: default_colors.R
                 , L: default_colors.L
                 , blank: default_colors.blank
                 , blank2: default_colors.blank2
                 , plastic: default_colors.plastic
                 , good_O: default_colors.good_O
                 , bg: default_colors.bg };

    var width_FBRL = (width / 2) / (distance + 1);
    var width_UD   = width_FBRL * 2;
    var slope_height = slope_width_to_height(width_FBRL);
    var height = (4 + 3 * distance) * slope_height;
    var visible_width_BL = distance && width_FBRL / distance;




    /* The UFR corner -- all other points are defined relatively to this. */
    var point_UFR = { x : (1 + distance) * width_FBRL   + width * padding / 2
                    , y : (2 + distance) * slope_height + width * padding / 2 }

    function make_bg(points, left, top) {
        return new Polygon({ points: points
                           , color: colors.plastic
                           , stroke: colors.plastic
                           , strokeWidth: 0
                           , css: { position: "absolute"
                                  , left: d2px(left)
                                  , top: d2px(top) } }) }

    var bg_UFR = make_bg(points_UFR(width_FBRL * 2),
                         point_UFR.x - width_FBRL,
                         point_UFR.y - 2 * slope_height);
    var bg_D = make_bg(points_UD(width_FBRL * 2),
                       point_UFR.x - width_FBRL,
                       point_UFR.y + distance * 2 * slope_height);
    var bg_B = make_bg(points_FB(width_FBRL),
                       point_UFR.x + distance * width_FBRL,
                       point_UFR.y - (2 + distance) * slope_height);
    var bg_L = make_bg(points_RL(width_FBRL),
                       point_UFR.x - (1 + distance) * width_FBRL,
                       point_UFR.y - (2 + distance) * slope_height);

    var bgs = [bg_UFR, bg_D, bg_B, bg_L];




    /* * * * * * * * * * * * * * * * *
     * UFR UF UFL UR U UL UBR UB UBL *
     * DFR DF DFL DR D DL DBR DB DBL *
     * FUR FU FUL FR F FL FDR FD FDL *
     * BUR BU BUL BR B BL BDR BD BDL *
     * RUF RU RUB RF R RB RDF RD RDB *
     * LUF LU LUB LF L LB LDF LD LDB *
     * * * * * * * * * * * * * * * * */

    var sticker_points = sticker_points_UD(width_UD  )
                 .concat(sticker_points_UD(width_UD  ))
                 .concat(sticker_points_FB(width_FBRL))
                 .concat(sticker_points_FB(width_FBRL))
                 .concat(sticker_points_RL(width_FBRL))
                 .concat(sticker_points_RL(width_FBRL));
    var sticker_sides = new Array(54);
    var stickers = new Array(54);
    var sticker_ratio = (1 - margin) * (1 / 3);

    function s2c(side) { return colors[side] }
    function make_sticker(width, shape, side, left, top) {
        return new Sticker({ width: width
                           , shape: shape
                           , side: side
                           , side_to_color: s2c
                           , css: { position: "absolute"
                                  , left: d2px(left)
                                  , top: d2px(top) } }) }

    for (var i = 0 * 9; i < 1 * 9; i++) {
        sticker_sides[i] = "U";
        stickers[i] = make_sticker(width_UD * sticker_ratio, "UD", "U",
            point_UFR.x - width_FBRL + sticker_points[i].x,
            point_UFR.y - 2 * slope_height + sticker_points[i].y) }

    for (var i = 1 * 9; i < 2 * 9; i++) {
        sticker_sides[i] = "D";
        stickers[i] = make_sticker(width_UD * sticker_ratio, "UD", "D",
            point_UFR.x - width_FBRL + sticker_points[i].x,
            point_UFR.y + distance * 2 * slope_height + sticker_points[i].y) }

    for (var i = 2 * 9; i < 3 * 9; i++) {
        sticker_sides[i] = "F";
        stickers[i] = make_sticker(width_FBRL * sticker_ratio, "FB", "F",
            point_UFR.x - width_FBRL + sticker_points[i].x,
            point_UFR.y - slope_height + sticker_points[i].y) }

    for (var i = 3 * 9; i < 4 * 9; i++) {
        sticker_sides[i] = "B";
        stickers[i] = make_sticker(width_FBRL * sticker_ratio, "FB", "B",
            point_UFR.x + distance * width_FBRL + sticker_points[i].x,
            point_UFR.y - (2 + distance) * slope_height + sticker_points[i].y) }

    for (var i = 4 * 9; i < 5 * 9; i++) {
        sticker_sides[i] = "R";
        stickers[i] = make_sticker(width_FBRL * sticker_ratio, "RL", "R",
            point_UFR.x + sticker_points[i].x,
            point_UFR.y - slope_height + sticker_points[i].y) }

    for (var i = 5 * 9; i < 6 * 9; i++) {
        sticker_sides[i] = "L";
        stickers[i] = make_sticker(width_FBRL * sticker_ratio, "RL", "L",
            point_UFR.x - (1 + distance) * width_FBRL + sticker_points[i].x,
            point_UFR.y - (2 + distance) * slope_height + sticker_points[i].y) }




    var cube_div =
      $("<div/>").addClass("cube ui-corner-all ui-widget-content")
                 .css({ width:  d2px(width  * (1 + padding)),
                        height: d2px(height * (1 + padding)) });

    function add_img(i,elem) { cube_div.append(elem.img) }

    cube_div.append(bg_D.img);
    jQuery.each(stickers.slice(1 * 9, 2 * 9), add_img);
    cube_div.append(bg_B.img);
    jQuery.each(stickers.slice(3 * 9, 4 * 9), add_img);
    cube_div.append(bg_L.img);
    jQuery.each(stickers.slice(5 * 9, 6 * 9), add_img);
    cube_div.append(bg_UFR.img);
    jQuery.each(stickers.slice(0 * 9, 1 * 9), add_img);
    jQuery.each(stickers.slice(2 * 9, 3 * 9), add_img);
    jQuery.each(stickers.slice(4 * 9, 5 * 9), add_img);

    for (var i = 0; i < sticker_sides.length; i++) {
        stickers[i].img.bind("mousedown", (function (i) { return function () {
            if (self.selected_side !== null &&
                self.selected_side !== sticker_sides[i]) {
                stickers[i].img.fadeOut(100);
                setTimeout(function () {
                               sticker_sides[i] = self.selected_side;
                               stickers[i].side(self.selected_side);
                               stickers[i].img.fadeIn(100) },
                           150) } } })(i)) };




    self.stickers = stickers;
    self.selected_side = null;
    var sticker_buttons_table = $("<table/>");
    var size = width / 12;
    var sticker_buttons = jQuery(["U","D","F","B","R","L",
                                  "blank","plastic"/*,"bg"*/])
        .map(function (i, side) {
            var s = new Sticker({
                width: size, shape: "square", side: side,
                side_to_color: s2c,
                stroke: colors.plastic,
                strokeWidth: size / 9 });
            s.img.ColorPicker({
                    color: colors[side],
                    eventName: "dblclick",
                    onSubmit: function (hsb, color, rgb) {
                        self.change_color(side, "#" + color) } });
            return s });

    for (var i = 0; i < sticker_buttons.length; i++) {
        var sb = sticker_buttons[i];
        var h = function (side) { return side === "blank" ? "?" :
                                         side === "plastic" ? "cube" : side };
        sticker_buttons_table.append($("<tr/>")
            .append($("<td/>").append($(sb.img)))
            .append($("<th/>").append($("<span>" + h(sb.side()) + "<span/>")))
            .bind("click", (function (i,sb) { return function () {
                    self.selected_side = i < 7 ? sb.side() : null;
                    jQuery(sticker_buttons).each(function () {
                        this.img.parent().parent()
                            .removeClass("selected_side") });
                    if (i < 7) {
                        $(this).addClass("selected_side") } } })(i,sb))) }




    var sticker_buttons_div =
      $("<div/>").addClass("sticker_buttons ui-corner-all ui-widget-content")
                 .append(sticker_buttons_table);

    var solver_list = $("<ul/>").addClass("solver_list");
    var solver_container = $("<div/>").addClass("solver_container")
                               .append(solver_list);

//    /* TODO: add an option somewhere to switch this off */
//    var show_step_onmouseover = true;

    var add_solver = function (options) {
        var waiting_for_response = false;
        var id = (options.longtitle || options.title).replace(/\W/g, "_");
        var backup = new Array(54);
        solver_list.append($("<li/>")
//            .bind("mouseover", function () {
//                if (options.sides) {
//                    backup = copy_sticker_sides();
//                    self.set_sticker_sides(options.sides);
//                    self.flush_stickers() } })
//            .bind("mouseout", function () {
//                if (options.sides) {
//                    self.set_sticker_sides(backup);
//                    self.flush_stickers() } })
            .append($("<a/>")
                .attr("href", "#" + id)
                .append($("<span/>")
                    .html(options.title))));
        var table = $("<table/>").addClass("solutions_table");
        var selects = {};
        var flat_places = [];

        for (var i = 0; i < options.places.length; i++) {
            var tr1 = $("<tr/>");
            var tr2 = $("<tr/>");
            for (var j = 0; j < options.places[i].length; j++) {
                 var place = options.places[i][j].replace(/</g, "&lt;")
                                                 .replace(/>/g, "&gt;");
                 flat_places[flat_places.length] = place;
                 tr1.append($("<th/>").html("<span>" + place + "</span>"));
                 selects[place] = $("<select/>")
                         .append($("<option>-</option>"))
                         .bind("change", function () {
                             var ix = this.selectedIndex;
                             if (ix > 0) {
                                 var str = this.options[ix].value;
                                 set_pos();
                                 self.apply_move_string(str) } });
                 tr2.append($("<td/>").append(selects[place])) }
            table.append(tr1).append(tr2) }

        var add_solutions = function (place, fields) {
            if (selects[place]) {
                if (fields.length < 3) {
                    selects[place].html("<option>" + fields.join(" ") +
                                        "</option>") }
                else {
                    var len   = fields[0],
                        count = fields[1],
                        sols  = fields.slice(2);
                    var title = len + ", " + String(count) +
                                           (count == 1 ? " solution"
                                                       : " solutions");
                    selects[place].html("<option>" + title + "</option>");
                    for (var i = 0; i < sols.length; i++) {
                        selects[place].append($("<option>" + sols[i] +
                                                "</option>")) } } } };
        var handle_request = function (string) {
            waiting_for_response = false;
            if (string.match(/^ERROR/i)) {
                for (var i = 0; i < flat_places.length; i++) {
                    selects[flat_places[i]].html("<option>-</option>") }
                if (string.match(/^ERROR:/i)) {
                    alert(string) } }
            else {
                jQuery.each(string.split(/\n/), function (i,sols) {
                    add_solutions(flat_places[i], sols.split(/\|/)) }) } };

        var position = copy_sticker_sides();
        function set_pos() {
            self.set_sticker_sides(position);
            self.flush_stickers() };
        /*var pre = $("<pre/>").html("EP: - - - - - - - - - - - -\n" +
                                   "EO: - - - - - - - - - - - -\n" +
                                   "CP: - - - - - - - -\n" +
                                   "CO: - - - - - - - -");*/
        solver_container.append($("<div/>")
            .addClass("solver_tab")
            .attr("id", id)
            .append($("<div/>")
                .addClass("solver_left_div")
                .append($("<p/>").append($("<a href=\"#\">What?</a>")
                    .bind("mouseover", function () {
                        if (options.sides) {
                            backup = copy_sticker_sides();
                            self.set_sticker_sides(options.sides);
                            self.flush_stickers() } })
                    .bind("mouseout", function () {
                        if (options.sides) {
                            self.set_sticker_sides(backup);
                            self.flush_stickers() } })))
                .append($("<p/>").append(make_button("Solve", function () {
                    if (waiting_for_response === true) {
                        alert("Only one request at a time!") }
                    else {
                        waiting_for_response = true;
                        position = copy_sticker_sides();
                        /*pre.html(piece_string());*/
                        for (var i = 0; i < flat_places.length; i++) {
                            selects[flat_places[i]].html("<option>Loading..." +
                                                         "</option>") }
                        $.post(options.url,
                               position.join(" ")
                                   .replace(/blank/g, "?"),
                               handle_request) } })))
                /*.append(pre)*/
                .append($("<p/>")
                    .append(make_button("Go back", set_pos))))
            .append(table)) };

    var right_div = $("<div/>").addClass("right_div")
        .append($("<div/>")
            .addClass("ui-corner-all ui-widget-content")
            .append($("<p/>")
                .append($("<textarea/>")
                    .attr({ id: "move_input", rows: 4, cols: 40 })))
            .append($("<p/>").append(make_button("Apply moves", function () {
                self.apply_move_string($("#move_input").attr("value")) }))))
        .append($("<div/>")
            .addClass("box_div ui-corner-all ui-widget-content")
            .append($("<p/>").append(make_button("Blank", function () {
                self.set_sticker_sides(blank_stickers);
                self.flush_stickers() })))
            .append($("<p/>").append(make_button("Solved", function () {
                self.set_sticker_sides(solved_stickers);
                self.flush_stickers() })))
            .append($("<p/>").append(make_button("Scramble", function () {
                var scramble = gen_scramble(25);
                self.set_sticker_sides(solved_stickers);
		self.flush_stickers();
                self.apply_move_string(moves_to_string(scramble));
		self.flush_stickers();
		$('#move_input').val('Scramble: ' + moves_to_string(scramble) + '\n\n' + $('#move_input').val().replace(/Scramble: [^\n]*\n\n/g, '')); }))))
        .append($("<div/>")
            .addClass("help_div ui-corner-all ui-widget-content")
            .append(make_button("Help!", function () {
                alert("The cube can be turned with mostly the same" +
                      " keys as Ryan Heise's simulators:\n\n" +
                      "U: J\t\tU': F\t\t" +
                      "D: L\t\tD': S\n" +
                      "F: H\t\tF': G\t\t" +
                      "B: W\t\tB': O\n" +
                      "R: I\t\tR': K\t\t" +
                      "L: D\t\tL': E\n" +
                      "\n" +
                      "r: U\t\tr': M\t\t" +
                      "l: R\t\tl': V\n" +
                      "\n" +
                      "y: ;\t\ty': A\n" +
                      "z: P\t\tz': Q\n" +
                      "x: Y\t\tz': N\n" +
                      "\n" +
                      "There's also Shift for rotating the whole cube" +
                      " and Alt for wide (double layer) turns." ) })));

    var cube_display_div = $("<div/>")
        .addClass("cube_display")
        .append(sticker_buttons_div)
        .append(cube_div)
        .append(right_div)
        .append(solver_container)
/*        .append($("<div class=\"contact_div ui-corner-all ui-widget-content" +
                  "\">This page isn't finished. Feel free to " +
                  "<a href=\"mailto:johannes.laire@gmail.com\">email me</a> " +
                  "with any questions or suggestions.</div>"))*/;

    for (var i = 0; i < solver_data.length; i++) {
        add_solver(solver_data[i]) }

    self.sticker_buttons_div = sticker_buttons_div;
    self.cube_div = cube_div;
    self.solver_container = solver_container;
    self.cube_display_div = cube_display_div;




    var side_indices = { U: 0, D: 1, F: 2,  B: 3, R: 4, L: 5
                       , blank: 6, plastic: 7, bg: 8 };

    self.change_color = function (side, color) {
        colors[side] = color;
        if (side === "plastic") {
            sticker_buttons[side_indices[side]].side(side);
            jQuery(bgs).each(function () { this.options({ color: color
                                                        , stroke: color }) });
            jQuery(sticker_buttons).each(function () {
                this.stroke(color) }) }
        else if (side === "bg") {
            sticker_buttons[side_indices[side]].options({
                side: side, stroke: colors.plastic });
            cube_div.css("background", color) }
        else {
            sticker_buttons[side_indices[side]].options({
                side: side, stroke: colors.plastic });
            jQuery(self.stickers).each(function () {
                if (this.side() == side) {
                    this.side(side) } }) }
        return self }

    self.appendTo = function (elem) {
        elem.append(cube_display_div);
        solver_container.tabs();
        return self };

    self.set_sticker_sides_nocopy = function (sides) {
        sticker_sides = sides;
        return self };

    self.set_sticker_sides = function (sides) {
        for (var i = 0; i < sticker_sides.length; i++) {
            sticker_sides[i] = sides[i] }
        return self };

    self.flush_stickers = function () {
        for (var i = 0; i < sticker_sides.length; i++) {
            if (stickers[i].side() !== sticker_sides[i]) {
                stickers[i].side(sticker_sides[i]) } }
        return self };

    self.permute = function (p) {
        var old_sides = new Array(54);
        for (var i = 0; i < stickers.length; i++) {
            if (sticker_sides[i] !== (old_sides[p[i]] || sticker_sides[p[i]])) {
                old_sides[i] = sticker_sides[i];
                sticker_sides[i] = old_sides[p[i]] || sticker_sides[p[i]] } }
        return self };

    self.apply_move = function (move) {
        for (var i = 0; i < move.length; i++) {
           var m = move[i];
           self.permute(permutations[m.side][m.depth][m.times]) }
        return self };

    self.apply_moves = function (moves) {
        for (var i = 0; i < moves.length; i++) {
            self.apply_move(moves[i]) }
        return self };

    self.apply_move_string = function (string) {
        jQuery.each(parse_moves(string), function (foobarbaz, move) {
            self.apply_move(move) });
        self.flush_stickers() };




    function copy_sticker_sides() {
        var out = new Array(sticker_sides.length);
        for (var i = 0; i < sticker_sides.length; i++) {
            out[i] = sticker_sides[i] }
        return out }

    function pieces() {
        return stickers_to_pieces(sticker_sides) }
    self.pieces = pieces;

    function piece_string2() {
        var unzipped = unzip_pieces(pieces());
        return unzipped.ep.join(",") + "|" +
               unzipped.eo.join(",") + "|" +
               unzipped.cp.join(",") + "|" +
               unzipped.co.join(",") }

    function piece_string() {
        var unzipped = unzip_pieces(pieces());
        return "EP: " + unzipped.ep.join(" ") + "\n" +
               "EO: " + unzipped.eo.join(" ") + "\n" +
               "CP: " + unzipped.cp.join(" ") + "\n" +
               "CO: " + unzipped.co.join(" ") }

/*    $(document).bind("keydown", { combi: "0", disableInInput: true },
        function () { alert(piece_string()) });*/
    $(document).bind("keydown", { combi: "space", disableInInput: true },
        function () {
            self.apply_move_string(moves_to_string(lame_scramble(50))) });

    $(document).bind("keydown", { combi: "1", disableInInput: true },
        function () { alert(sticker_sides.join(" ").replace(/blank/g, "?")) });




    function add_key(key, sides, depths, timess) {
        $(document).bind("keydown", { combi: key, disableInInput: true },
            function () {
                for (var i = 0; i < sides.length; i++) {
                    self.apply_move([{ side:  sides[i],
                                       depth: depths[i],
                                       times: timess[i] }]) }
                self.flush_stickers();
                return false }) }

    add_key("J", ["U"], [0], [1]);
    add_key("F", ["U"], [0], [3]);
    add_key("L", ["U"], [2], [1]);
    add_key("S", ["U"], [2], [3]);
    add_key("H", ["F"], [0], [1]);
    add_key("G", ["F"], [0], [3]);
    add_key("O", ["F"], [2], [1]);
    add_key("W", ["F"], [2], [3]);
    add_key("I", ["R"], [0], [1]);
    add_key("K", ["R"], [0], [3]);
    add_key("E", ["R"], [2], [1]);
    add_key("D", ["R"], [2], [3]);
    add_key(";", ["U","U","U"], [0,1,2], [1,1,1]);
    add_key("A", ["U","U","U"], [0,1,2], [3,3,3]);
    add_key("Y", ["R","R","R"], [0,1,2], [1,1,1]);
    add_key("N", ["R","R","R"], [0,1,2], [3,3,3]);
    add_key("P", ["F","F","F"], [0,1,2], [1,1,1]);
    add_key("Q", ["F","F","F"], [0,1,2], [3,3,3]);
    add_key("U", ["R","R"], [0,1], [1,1]);
    add_key("M", ["R","R"], [0,1], [3,3]);
    add_key("R", ["R","R"], [1,2], [1,1]);
    add_key("V", ["R","R"], [1,2], [3,3]);
    add_key("Shift+J", ["U","U","U"], [0,1,2], [1,1,1]);
    add_key("Shift+F", ["U","U","U"], [0,1,2], [3,3,3]);
    add_key("Shift+L", ["U","U","U"], [0,1,2], [1,1,1]);
    add_key("Shift+S", ["U","U","U"], [0,1,2], [3,3,3]);
    add_key("Shift+H", ["F","F","F"], [0,1,2], [1,1,1]);
    add_key("Shift+G", ["F","F","F"], [0,1,2], [3,3,3]);
    add_key("Shift+O", ["F","F","F"], [0,1,2], [1,1,1]);
    add_key("Shift+W", ["F","F","F"], [0,1,2], [3,3,3]);
    add_key("Shift+I", ["R","R","R"], [0,1,2], [1,1,1]);
    add_key("Shift+K", ["R","R","R"], [0,1,2], [3,3,3]);
    add_key("Shift+E", ["R","R","R"], [0,1,2], [1,1,1]);
    add_key("Shift+D", ["R","R","R"], [0,1,2], [3,3,3]);
    add_key("Alt+J", ["U","U"], [0,1], [1,1]);
    add_key("Alt+F", ["U","U"], [0,1], [3,3]);
    add_key("Alt+L", ["U","U"], [1,2], [1,1]);
    add_key("Alt+S", ["U","U"], [1,2], [3,3]);
    add_key("Alt+H", ["F","F"], [0,1], [1,1]);
    add_key("Alt+G", ["F","F"], [0,1], [3,3]);
    add_key("Alt+O", ["F","F"], [1,2], [1,1]);
    add_key("Alt+W", ["F","F"], [1,2], [3,3]);
    add_key("Alt+I", ["R","R"], [0,1], [1,1]);
    add_key("Alt+K", ["R","R"], [0,1], [3,3]);
    add_key("Alt+E", ["R","R"], [1,2], [1,1]);
    add_key("Alt+D", ["R","R"], [1,2], [3,3]);

    return self }


