$(document).ready(function() {
    var bitsyData = {};

    var palette = {
        id: 0,
        background: {
            red: 62, 
            green: 43, 
            blue: 32,
        },
        tile: {
            red: 208, 
            green: 112, 
            blue: 56,
        },
        sprite: {
            red: 229, 
            green: 92, 
            blue: 68,
        }
    };

    var room = [];

    var tiles = [];

    var croptions = {
        url: 'https://i.imgur.com/ThQZ94v.jpg',
        viewport: {width: 128, height: 128, type: 'square'},
        boundary: {width: 256, height: 256},
        zoom: 0
    }

    var $croppie = $('#croppie');
    var croppie  = $croppie.croppie(croptions);

    function colourDifference(colour1, colour2) {
        difference = {};

        _.each(['red', 'green', 'blue'], function(key) {
            difference[key] = Math.abs(colour1[key] - colour2[key]);
        });

        // sum rgb differences
        return _.reduce(difference, function(sum, n) {
          return sum + n;
        }, 0);
    }

    function zeroPad(input, desiredLength) {
        while (input.length < desiredLength) {
            input = "0" + input;
        }

        return input;
    }

    function colourToHex(colour) {
        return '#' + zeroPad(Number(colour.red  ).toString(16), 2) 
                   + zeroPad(Number(colour.green).toString(16), 2) 
                   + zeroPad(Number(colour.blue ).toString(16), 2);
    }

    function hexToColour(hex) {
        var rgb = hex.match(/[\da-fA-F]{2}/g);

        return {
            red:   parseInt(rgb[0], 16),
            green: parseInt(rgb[1], 16),
            blue:  parseInt(rgb[2], 16),
        };
    }

    function getClosestColour(initialColour, colourOptions) {
        // ditch sprite colour as we're not using it atm
        delete colourOptions.sprite;

        _.each(palette, function(colour, name) {
            colourOptions[name].name = name;
            colourOptions[name].difference = colourDifference(initialColour, colour);
        });

        // lowest difference (closest) wins
        return _.first(_.sortBy(colourOptions, 'difference'));
    }

    function handleBitsyGameData() {
        bitsyData = {};

        var input = $('#bitsy-data').val();

        // get palettes
        var palettes = input.match(/PAL (.*)\s(NAME (.*)\s)?([0-9,]*[\s]){3}/g);

        bitsyData.palettes = {};
        
        _.each(palettes, function(palette, n) {
            var thisPalette = {};
            var name = "";

            if (palette.match(/NAME (.+)\n/)) {
                name = palette.match(/NAME (.+)\n/)[0].replace('NAME ', '');
            } else if (palette.match(/PAL (\d+)\n/)) {
                name = palette.match(/PAL (\d+)\n/)[0].replace("PAL", "palette");
            }

            var colours = palette.match(/\d+,\d+,\d+/g);

            colours = _.map(colours, function(colour) {
                var rgb = colour.split(',');

                return {red: rgb[0], green: rgb[1], blue: rgb[2]};
            });

            bitsyData.palettes[name] = {
                id:         n,
                background: colours[0],
                tile:       colours[1],
                sprite:     colours[2],
            }
        });

        // get tiles
        bitsyData.tiles = {};

        var tiles = input.match(/TIL (.*)\n([01]{8}\n){8}/g);

        if (tiles.length > 0) {   
            for (var i = 0; i < tiles.length; i++) {
                var name = tiles[i].match(/TIL .*/)[0].replace('TIL ', '');
                
                tiles[i] = tiles[i].replace(/TIL .*\n/, '');

                var bitmap = tiles[i].match(/[01]/g);

                bitsyData.tiles[name] = {
                    name: name, 
                    bitmap: _.chunk(bitmap, 8)
                };
            }
        }

        // set palette to first imported palette and redraw
        palette = _.first(_.sortBy(bitsyData.palettes, 'id'));

        renderResult();

        // update palette picker
        $('tr.palette').remove();

        _.each(bitsyData.palettes, function(palette, name) {
            $('#palette tbody').append(
                '<tr class="palette">'
                  + '<td>'
                      + '<input type="radio" name="palette" id="palette-' + name + '">'
                      + '<input type="hidden" name="id" value="' + palette.id + '">'
                  + '</td>'
                  + '<td><label for="palette-' + name + '">' + name + '</label></td>'
                  + '<td><input type="color" name="background" value="' + colourToHex(palette.background) + '"></td>'
                  + '<td><input type="color" name="tile"       value="' + colourToHex(palette.tile)       + '"></td>'
                  + '<td><input type="color" name="sprite"     value="' + colourToHex(palette.sprite)     + '" disabled></td>'
              + '</tr>'
            );
        });

        $('tr.palette input[type="radio"]').eq(0).trigger('click');
    }

    function readFile(input) {
        if (input.files && input.files[0]) {
            var reader = new FileReader();
            
            reader.onload = function (e) {
                $croppie.croppie('bind', {
                    url: e.target.result,
                    zoom: 0
                });
            }
            
            reader.readAsDataURL(input.files[0]);
        }
    }

    var renderResult = _.debounce(function() {
        $croppie.croppie('result', {
            type: 'rawcanvas',
            size: 'viewport'
        }).then(function (result) {
            var imageData  = result.getContext('2d').getImageData(0, 0, 128, 128);
            var rawData    = imageData.data;
            var monochrome = [];

            brightnessAdjustment = parseFloat($('#brightness').val());

            // for each pixel
            for (var i = 0; i < rawData.length; i += 4) {
                // this brightness adjustment is pretty crude but whatever
                var pixel = {
                    red:   Math.min(rawData[i    ] + brightnessAdjustment, 255),
                    green: Math.min(rawData[i + 1] + brightnessAdjustment, 255),
                    blue:  Math.min(rawData[i + 2] + brightnessAdjustment, 255),
                };

                var targetColour = getClosestColour(pixel, palette);

                if (targetColour.name === "background") {
                    monochrome.push(0);
                } else { // tile
                    monochrome.push(1)
                }

                rawData[i    ] = targetColour.red;
                rawData[i + 1] = targetColour.green;
                rawData[i + 2] = targetColour.blue;
                rawData[i + 3] = 255;
            }

            // split monochrome bitmap into equal chunks for easier x:y access
            monochrome = _.chunk(monochrome, 128);

            document.getElementById('preview').getContext('2d').putImageData(imageData, 0, 0);

            // tiled output

            room = [];

            _.times(16, function(tileY) {
                _.times(16, function(tileX) {
                    // make pseudo-tile from monochrome bitmap
                    var pseudoTile = [];

                    _.times(8, function(y) {
                        pseudoTile.push(
                            _.slice(monochrome[(tileY * 8) + y], (tileX * 8), (tileX * 8) + 8)
                        );
                    })
                    
                    var tilesForMatch = bitsyData.tiles;

                    _.each(tilesForMatch, function(tile) {
                        tile.match = 0;

                        _.each(tile.bitmap, function(row, y) {
                            _.each(row, function(pixel, x) {
                                if (parseInt(pixel) === parseInt(pseudoTile[y][x])) {
                                    tile.match++;
                                }
                            });
                        });
                    });

                    var bestMatch = _.first(_.sortBy(tilesForMatch, 'match'));

                    // if best match is under threshold
                        // turn pseudo-tile into a real tile and add it to the tile data
                    
                    room.push(bestMatch.name);
                });
            });

            room = _.chunk(room, 16);

            // write room to output
            imageData = document.getElementById("room-output").getContext('2d').getImageData(0, 0, 128, 128);
            rawData   = imageData.data;

            _.each(room, function(row, tileY) {
                _.each(row, function(tileName, tileX) {
                    if (_.get(bitsyData, 'tiles.' + tileName + '.bitmap')) {
                        _.each(bitsyData.tiles[tileName].bitmap, function(row, y) {
                            _.each(row, function(pixel, x) {
                                var position = (((tileY * 8) + y) * 128) + ((tileX * 8) + x);

                                position *= 4; // 4 values (rgba) per pixel

                                if (parseInt(pixel) === 1) { // ?! wtf
                                    pixelColour = palette.background;
                                } else {
                                    pixelColour = palette.tile;
                                }

                                rawData[position    ] = pixelColour.red;
                                rawData[position + 1] = pixelColour.green;
                                rawData[position + 2] = pixelColour.blue;
                                rawData[position + 3] = 255;
                            });
                        });
                    }
                });
            });

            document.getElementById('room-output').getContext('2d').putImageData(imageData, 0, 0);
        });
    }, 30);

    $croppie.on('update', renderResult);

    // make this not debounced but called every n milliseconds
    $('#brightness').on('change', renderResult);

    $('#brightness').on('dblclick', function() {
        $(this).val(0);
        renderResult();
    });

    $('label[for="brightness"]').on('click touchdown', function() {
        $('#brightness').trigger('dblclick');
    });

    $('#bitsy-data').on('change blur keyup', handleBitsyGameData);

    handleBitsyGameData();

    $('#imageUpload').on('change', function () {
        readFile(this);
    });

    // these inputs get added and removed from the DOM so the event handler needs to be on the document
    $(document).on('change', '#palette input', function() {
        // if this is a colour input, update the palette
        if ($(this).attr('type') === 'color') {
            palette[$(this).attr('name')] = hexToColour($(this).val());
        }

        // if this is a radio button, pick this palette
        if ($(this).attr('type') === 'radio') {
            palette.id         = parseInt(   $(this).closest('.palette').find('input[name="id"]'        ).val());
            palette.background = hexToColour($(this).closest('.palette').find('input[name="background"]').val());
            palette.tile       = hexToColour($(this).closest('.palette').find('input[name="tile"]'      ).val());
            // sprite colour is not currently used
        }

        renderResult();
    });

    $('#save').on('click touchend', function() {
        var newGameData = $('textarea').val();

        // need to import IDs so we don't give the new room a conflicting ID
        var roomNames = newGameData.match(/ROOM \d+/g);

        var newRoomId = parseInt(_.last(roomNames).replace(/[^\d]+/g, "")) + 1;

        var newRoomName = $('#roomName').val();
        // remove invalid chars? what's invalid? newlines? are those possible?

        var newRoom = "ROOM " + newRoomId + "\n";

        _.each(room, function(row) {
            newRoom += _.toString(row) + "\n";
        });

        if (newRoomName) {
            newRoom += "NAME " + newRoomName + "\n";
        }

        newRoom += "PAL " + palette.id + "\n";

        // write
        $('textarea').val(newGameData.replace(/(ROOM .*\n(.*\n)*PAL .*)/g, '$1\n\n' + newRoom));
    });
});