var graph = new joint.dia.Graph();
var paper = new joint.dia.Paper({
    el: $('#paper'),
    width: 600,
    height: 400,
    gridSize: 10,
    drawGrid: true,
    model: graph,
    defaultConnectionPoint: { name: 'anchor' }
});

var elements = [

    new joint.shapes.standard.Path({
        position: { x: 75, y: 175 },
        size: { width: 100, height: 40 },
        attrs: {
            label: { text: 'joint' },
            body: { refD: 'M 0 0 L 100 0 80 20 100 40 0 40 Z' }
        }
    }),

    new joint.shapes.standard.Path({
        position: { x: 200, y: 275 },
        size: { width: 100, height: 40 },
        attrs: {
            label: { text: 'dia' },
            body: { refD: 'M 20 0 L 100 0 80 20 100 40 20 40 0 20 Z' }
        }
    }),

    new joint.shapes.standard.Path({
        position: { x: 200, y: 75 },
        size: { width: 100, height: 40 },
        attrs: {
            label: { text: 'util' },
            body: { refD: 'M 20 0 L 100 0 80 20 100 40 20 40 0 20 Z' }
        }
    }),

    new joint.shapes.standard.Path({
        position: { x: 200, y: 175 },
        size: { width: 100, height: 40 },
        attrs: {
            label: { text: 'shapes' },
            body: { refD: 'M 20 0 L 100 0 80 20 100 40 20 40 0 20 Z' }
        }
    }),

    new joint.shapes.standard.Path({
        position: { x: 325, y: 175 },
        size: { width: 100, height: 40 },
        attrs: {
            label: { text: 'basic' },
            body: { refD: 'M 20 0 L 100 0 80 20 100 40 20 40 0 20 Z' }
        }
    }),

    new joint.shapes.standard.Path({
        position: { x: 450, y: 150 },
        size: { width: 100, height: 40 },
        attrs: {
            label: { text: 'Path' },
            body: { refD: 'M 20 0 L 100 0 100 40 20 40 0 20 Z' }
        }
    }),

    new joint.shapes.standard.Path({
        position: { x: 450, y: 200 },
        size: { width: 100, height: 40 },
        attrs: {
            label: { text: 'Text' },
            body: { refD: 'M 20 0 L 100 0 100 40 20 40 0 20 Z' }
        }
    }),

    new joint.shapes.standard.Path({
        position: { x: 325, y: 250 },
        size: { width: 100, height: 40 },
        attrs: {
            label: { text: 'Paper' },
            body: { refD: 'M 20 0 L 100 0 100 40 20 40 0 20 Z' }
        }
    }),

    new joint.shapes.standard.Path({
        position: { x: 325, y: 300 },
        size: { width: 100, height: 40 },
        attrs: {
            label: { text: 'Graph' },
            body: { refD: 'M 20 0 L 100 0 100 40 20 40 0 20 Z' }
        }
    }),

    new joint.shapes.standard.Path({
        position: { x: 325, y: 100 },
        size: { width: 100, height: 40 },
        attrs: {
            label: { text: 'getByPath' },
            body: { refD: 'M 20 0 L 100 0 100 40 20 40 0 20 Z' }
        }
    }),

    new joint.shapes.standard.Path({
        position: { x: 325, y: 50 },
        size: { width: 100, height: 40 },
        attrs: {
            label: { text: 'setByPath' },
            body: { refD: 'M 20 0 L 100 0 100 40 20 40 0 20 Z' }
        }
    })
];

// add all elements to the graph
graph.resetCells(elements);

var linkEnds = [
    { source: 0, target:  1 }, { source: 0, target: 2 }, { source: 0, target: 3 },
    { source: 1, target:  7 }, { source: 1, target: 8 },
    { source: 2, target:  9 }, { source: 2, target: 10 },
    { source: 3, target:  4 },
    { source: 4, target:  5 }, { source: 4, target:  6 }
];

// add all links to the graph
linkEnds.forEach(function(ends) {
    new joint.shapes.standard.Link({
        source: { id: elements[ends.source].id },
        target: { id: elements[ends.target].id },
        z: -1 // make sure all links are displayed under the elements
    }).addTo(graph);
});

// cache important html elements
var $ox = $('#ox');
var $oy = $('#oy');
var $sx = $('#sx');
var $sy = $('#sy');
var $w = $('#width');
var $h = $('#height');
var $ftcPadding = $('#ftc-padding');
var $ftcGridW = $('#ftc-grid-width');
var $ftcGridH = $('#ftc-grid-height');
var $ftcNewOrigin = $('#ftc-new-origin');
var $stfPadding = $('#stf-padding');
var $stfMinScale = $('#stf-min-scale');
var $stfMaxScale = $('#stf-max-scale');
var $stfScaleGrid = $('#stf-scale-grid');
var $stfRatio = $('#stf-ratio');
var $bboxX = $('#bbox-x');
var $bboxY = $('#bbox-y');
var $bboxW = $('#bbox-width');
var $bboxH = $('#bbox-height');
var $grid = $('#grid');

// cache important svg elements
var svg = V(paper.svg);
var svgVertical = V('path').attr('d', 'M -10000 -1 L 10000 -1');
var svgHorizontal = V('path').attr('d', 'M -1 -10000 L -1 10000');
var svgRect = V('rect');
var svgAxisX = svgVertical.clone().addClass('axis');
var svgAxisY = svgHorizontal.clone().addClass('axis');
var svgBBox = svgRect.clone().addClass('bbox');

svgBBox.hide = joint.util.debounce(function() {
    svgBBox.removeClass('active');
}, 500);

// svg Container - contains all non-jointjs svg elements
var svgContainer = [];

svgContainer.showAll = function() {
    this.forEach(function(v) { v.addClass('active'); });
};

svgContainer.hideAll = function() {
    this.forEach(function(v) { v.removeClass('active'); });
};

svgContainer.removeAll = function() {
    while (this.length > 0) {
        this.pop().remove();
    }
};

// Axis has to be appended to the svg, so it won't affect the viewport.
svg.append([svgAxisX, svgAxisY, svgBBox]);

function fitToContent() {

    svgContainer.removeAll();

    var padding = parseInt($ftcPadding.val(), 10);
    var gridW = parseInt($ftcGridW.val(), 10);
    var gridH = parseInt($ftcGridH.val(), 10);
    var allowNewOrigin = $ftcNewOrigin.val();

    paper.fitToContent({
        padding: padding,
        gridWidth: gridW,
        gridHeight: gridH,
        allowNewOrigin: allowNewOrigin
    });

    var bbox = paper.getContentBBox();

    var translatedX = allowNewOrigin == 'any' || (allowNewOrigin == 'positive' && bbox.x - paper.options.origin.x >= 0) || (allowNewOrigin == 'negative' && bbox.x - paper.options.origin.x < 0);
    var translatedY = allowNewOrigin == 'any' || (allowNewOrigin == 'positive' && bbox.y - paper.options.origin.y >= 0) || (allowNewOrigin == 'negative' && bbox.y - paper.options.origin.y < 0);

    if (padding) {

        var svgPaddingRight = svgHorizontal.clone().addClass('padding')
            .translate(paper.options.width - padding / 2, 0, { absolute: true })
            .attr('stroke-width', padding);

        var svgPaddingBottom = svgVertical.clone().addClass('padding')
            .translate(0, paper.options.height - padding / 2, { absolute: true })
            .attr('stroke-width', padding);

        svg.append([svgPaddingBottom, svgPaddingRight]);
        svgContainer.push(svgPaddingBottom, svgPaddingRight);
    }

    if (padding && (translatedX || translatedY)) {

        var paddings = [];

        if (translatedY) {

            var svgPaddingTop = svgVertical.clone().addClass('padding')
                .translate(0, padding / 2, { absolute: true })
                .attr('stroke-width', padding);

            paddings.push(svgPaddingTop);
        }

        if (translatedX) {

            var svgPaddingLeft = svgHorizontal.clone().addClass('padding')
                .translate(padding / 2, 0, { absolute: true })
                .attr('stroke-width', padding);

            paddings.push(svgPaddingLeft);
        }

        if (paddings.length) {
            svg.append(paddings);
            svgContainer.push.apply(svgContainer, paddings);
        }
    }

    if (gridW > 2) {

        var x = gridW;

        if (translatedX) x += padding;

        do {

            var svgGridX = svgHorizontal.clone().translate(x, 0, { absolute: true }).addClass('grid');
            svg.append(svgGridX);
            svgContainer.push(svgGridX);

            x += gridW;

        } while (x < paper.options.width - padding);
    }

    if (gridH > 2) {

        var y = gridH;

        if (translatedY) y += padding;

        do {

            var svgGridY = svgVertical.clone().translate(0, y, { absolute: true }).addClass('grid');
            svg.append(svgGridY);
            svgContainer.push(svgGridY);
            y += gridH;

        } while (y < paper.options.height - padding);
    }

    svgContainer.showAll();
}

function scaleToFit() {

    svgContainer.removeAll();

    var padding = parseInt($stfPadding.val(), 10);

    paper.scaleContentToFit({
        padding: padding,
        minScale: parseFloat($stfMinScale.val()),
        maxScale: parseFloat($stfMaxScale.val()),
        scaleGrid: parseFloat($stfScaleGrid.val()),
        preserveAspectRatio: $stfRatio.is(':checked')
    });

    paper.viewport.getBoundingClientRect(); // MS Edge hack to fix the invisible text.

    if (padding) {

        var svgPaddingRight = svgHorizontal.clone().addClass('padding')
            .translate(paper.options.width - padding / 2, 0, { absolute: true })
            .attr('stroke-width', padding);

        var svgPaddingBottom = svgVertical.clone().addClass('padding')
            .translate(0, paper.options.height - padding / 2, { absolute: true })
            .attr('stroke-width', padding);

        var svgPaddingLeft = svgVertical.clone().addClass('padding')
            .translate(0, padding / 2, { absolute: true })
            .attr('stroke-width', padding);

        var svgPaddingTop = svgHorizontal.clone().addClass('padding')
            .translate(padding / 2, 0, { absolute: true })
            .attr('stroke-width', padding);

        svg.append([svgPaddingBottom, svgPaddingRight, svgPaddingTop, svgPaddingLeft]);
        svgContainer.push(svgPaddingBottom, svgPaddingRight, svgPaddingTop, svgPaddingLeft);
    }

    svgContainer.showAll();
}

function updateBBox() {

    var bbox = paper.getContentBBox();

    $bboxX.text(Math.round(bbox.x - paper.options.origin.x));
    $bboxY.text(Math.round(bbox.y - paper.options.origin.y));
    $bboxW.text(Math.round(bbox.width));
    $bboxH.text(Math.round(bbox.height));

    svgBBox.attr(bbox).addClass('active').hide();
}

/* events */

$('#fit-to-content input, #fit-to-content select').on('input change', fitToContent);
$('#scale-to-fit input').on('input change', scaleToFit);

$ox.on('input change', function() {
    paper.setOrigin(parseInt(this.value, 10), parseInt($oy.val(), 10));
});
$oy.on('input change', function() {
    paper.setOrigin(parseInt($ox.val(), 10), parseInt(this.value, 10));
});
$sx.on('input change', function() {
    paper.scale(parseFloat(this.value), parseFloat($sy.val()));
});
$sy.on('input change', function() {
    paper.scale(parseFloat($sx.val()), parseFloat(this.value));
});
$w.on('input change', function() {
    paper.setDimensions(parseInt(this.value, 10), parseInt($h.val(),10));
});
$h.on('input change', function() {
    paper.setDimensions(parseInt($w.val(), 10), parseInt(this.value, 10));
});
$grid.on('input change', function() {
    paper.options.gridSize = this.value;
    paper.drawGrid();
});
$('.range').on('input change', function() {
    $(this).next().text(this.value);
});

paper.on({

    scale: function(sx, sy) {

        $sx.val(sx).next().text(sx.toFixed(2));
        $sy.val(sy).next().text(sy.toFixed(2));

        svgContainer.hideAll();
    },

    translate: function(ox, oy) {

        $ox.val(ox).next().text(Math.round(ox));
        $oy.val(oy).next().text(Math.round(oy));

        // translate axis
        svgAxisX.translate(0, oy, { absolute: true });
        svgAxisY.translate(ox, 0, { absolute: true });

        svgContainer.hideAll();
    },

    resize: function(width, height) {

        $w.val(width).next().text(Math.round(width));
        $h.val(height).next().text(Math.round(height));

        svgContainer.hideAll();
    }
});

graph.on('change', function() {
    svgContainer.hideAll();
    updateBBox();
});

updateBBox();

var bgImageDataURL = 'data:image/png;base64, ';

$('#bg-toggle, #bg-color, #bg-repeat, #bg-opacity, #bg-size, #bg-position').on('change input', function() {
    paper.drawBackground({
        color: $('#bg-color').val(),
        image: $('#bg-toggle').is(':checked') ? bgImageDataURL : '',
        position: JSON.parse($('#bg-position').val().replace(/'/g, '"')),
        size: JSON.parse($('#bg-size').val().replace(/'/g, '"')),
        repeat: $('#bg-repeat').val(),
        opacity: $('#bg-opacity').val()
    });
});

var _inputRenderer = function(gridTypes, onChange) {

    var currentOpt = {};
    var formTypes = {
        'color': function(inputDef, container) {
            var input = $('<input/>', { type: 'color' }).val(inputDef.value).on('change input', function() {
                inputDef.onChange($(this).val(), currentOpt);
                onChange(currentOpt);
            }).trigger('change');
            container.append($('<label/>').text(inputDef.name));
            container.append(input);
        },
        'number': function(inputDef, container) {
            var input = $('<input/>', { type: 'range' })
                .val(inputDef.value)
                .attr({
                    step: inputDef.step,
                    min: inputDef.min,
                    max: inputDef.max
                })
                .on('change input', function() {
                    var value = parseFloat($(this).val()).toFixed(2);
                    $('output', $(this).parent()).text(value);
                    inputDef.onChange(value, currentOpt);
                    onChange(currentOpt);
                }).trigger('change');
            container.append($('<label/>').text(inputDef.name));
            container.append(input);
            container.append($('<output/>').text(input.val()));
        }
    };

    var renderInput =  function(formType, container) {
        return formTypes[formType.type](formType, container);
    };

    return {
        renderSettings: function(gridTypeName) {
            currentOpt.name = gridTypeName;
            currentOpt.args = [{}, {}];
            gridTypes[gridTypeName].inputs.forEach(function(x) {
                var element = $('<div/>').addClass('form-group').appendTo($gridTypesOpt);
                renderInput(x, element);
            });
            onChange(currentOpt);
        }
    };
};

var gridTypes = {
    'dot': {
        inputs: [{
            type: 'color', name: 'Color', value: '#000000',
            onChange: function(value, ref) {
                ref.args[0].color = value;
            }
        }, {
            type: 'number', name: 'Thickness', value: 1, step: 0.5, min: 0.5, max: 10,
            onChange: function(value, ref) {
                ref.args[0].thickness = value;
            }
        }]
    },
    'fixedDot': {
        inputs: [{
            type: 'color', name: 'Color', value: '#000000',
            onChange: function(value, ref) {
                ref.args[0].color = value;
            }
        }, {
            type: 'number', name: 'Thickness', value: 1, step: 0.5, min: 0.5, max: 10,
            onChange: function(value, ref) {
                ref.args[0].thickness = value;
            }
        }]
    },
    'mesh': {
        inputs: [{
            type: 'color', name: 'Color', value: '#000000',
            onChange: function(value, ref) {
                ref.args[0].color = value;
            }
        }, {
            type: 'number', prop: 'thickness', name: 'Thickness', value: 1, step: 0.5, min: 0.5, max: 10,
            onChange: function(value, ref) {
                ref.args[0].thickness = value;
            }
        }]
    },
    'doubleMesh': {
        inputs: [{
            type: 'color', name: 'Primary Color', value: '#AAAAAA',
            onChange: function(value, ref) {
                ref.args[0].color = value;
            }
        }, {
            type: 'number', name: 'Primary Thickness', value: 1, step: 0.5, min: 0.5, max: 5,
            onChange: function(value, ref) {
                ref.args[0].thickness = value;
            }
        }, {
            type: 'color', name: 'Secondary Color', value: '#000000',
            onChange: function(value, ref) {
                ref.args[1].color = value;
            }
        }, {
            type: 'number', name: 'Secondary Thickness', value: 3, step: 0.5, min: 0.5, max: 5,
            onChange: function(value, ref) {
                ref.args[1].thickness = value;
            }
        }, {
            type: 'number', name: 'Scale Factor', value: 5, step: 1, min: 1, max: 10,
            onChange: function(value, ref) {
                ref.args[1].scaleFactor = value;
            }
        }]
    }
};
var renderer = _inputRenderer(gridTypes, function(gridOpt) {

    paper.setGrid(gridOpt);
    paper.drawGrid();
});

var $gridTypesOpt = $('.grid-types-opt');

$('#grid-type').on('change input', function() {

    $gridTypesOpt.empty();
    renderer.renderSettings($(this).val());
});

renderer.renderSettings($('#grid-type').val());

