// Source: https://raw.githubusercontent.com/yoannmoinet/nipplejs/5a8f8bacea3776630a270a25dbbd760c9451bb55/src/nipple.js

import Super from './super';
import * as u from './utils';

///////////////////////
///   THE NIPPLE    ///
///////////////////////

function Nipple (collection, options) {
    this.identifier = options.identifier;
    this.position = options.position;
    this.frontPosition = options.frontPosition;
    this.collection = collection;

    // Defaults
    this.defaults = {
        size: 100,
        threshold: 0.1,
        color: 'white',
        fadeTime: 250,
        dataOnly: false,
        restJoystick: true,
        restOpacity: 0.5,
        mode: 'dynamic',
        zone: document.body,
        lockX: false,
        lockY: false,
        shape: 'circle'
    };

    this.config(options);

    // Overwrites
    if (this.options.mode === 'dynamic') {
        this.options.restOpacity = 0;
    }

    this.id = Nipple.id;
    Nipple.id += 1;
    this.buildEl()
        .stylize();

    // Nipple's API.
    this.instance = {
        el: this.ui.el,
        on: this.on.bind(this),
        off: this.off.bind(this),
        show: this.show.bind(this),
        hide: this.hide.bind(this),
        add: this.addToDom.bind(this),
        remove: this.removeFromDom.bind(this),
        destroy: this.destroy.bind(this),
        setPosition:this.setPosition.bind(this),
        resetDirection: this.resetDirection.bind(this),
        computeDirection: this.computeDirection.bind(this),
        trigger: this.trigger.bind(this),
        position: this.position,
        frontPosition: this.frontPosition,
        ui: this.ui,
        identifier: this.identifier,
        id: this.id,
        options: this.options
    };

    return this.instance;
}

Nipple.prototype = new Super();
Nipple.constructor = Nipple;
Nipple.id = 0;

// Build the dom element of the Nipple instance.
Nipple.prototype.buildEl = function (options) {
    this.ui = {};

    if (this.options.dataOnly) {
        return this;
    }

    this.ui.el = document.createElement('div');
    this.ui.back = document.createElement('div');
    this.ui.front = document.createElement('div');

    this.ui.el.className = 'nipple collection_' + this.collection.id;
    this.ui.back.className = 'back';
    this.ui.front.className = 'front';

    this.ui.el.setAttribute('id', 'nipple_' + this.collection.id +
        '_' + this.id);

    this.ui.el.appendChild(this.ui.back);
    this.ui.el.appendChild(this.ui.front);

    return this;
};

// Apply CSS to the Nipple instance.
Nipple.prototype.stylize = function () {
    if (this.options.dataOnly) {
        return this;
    }
    var animTime = this.options.fadeTime + 'ms';
    var borderStyle = u.getVendorStyle('borderRadius', '50%');
    var transitStyle = u.getTransitionStyle('transition', 'opacity', animTime);
    var styles = {};
    styles.el = {
        position: 'absolute',
        opacity: this.options.restOpacity,
        display: 'block',
        'zIndex': 999
    };

    styles.back = {
        position: 'absolute',
        display: 'block',
        width: this.options.size + 'px',
        height: this.options.size + 'px',
        marginLeft: -this.options.size / 2 + 'px',
        marginTop: -this.options.size / 2 + 'px',
        background: this.options.color,
        'opacity': '.5'
    };

    styles.front = {
        width: this.options.size / 2 + 'px',
        height: this.options.size / 2 + 'px',
        position: 'absolute',
        display: 'block',
        marginLeft: -this.options.size / 4 + 'px',
        marginTop: -this.options.size / 4 + 'px',
        background: this.options.color,
        'opacity': '.5'
    };

    u.extend(styles.el, transitStyle);
    if(this.options.shape === 'circle'){
        u.extend(styles.back, borderStyle);
    }
    u.extend(styles.front, borderStyle);

    this.applyStyles(styles);

    return this;
};

Nipple.prototype.applyStyles = function (styles) {
    // Apply styles
    for (var i in this.ui) {
        if (this.ui.hasOwnProperty(i)) {
            for (var j in styles[i]) {
                this.ui[i].style[j] = styles[i][j];
            }
        }
    }

    return this;
};

// Inject the Nipple instance into DOM.
Nipple.prototype.addToDom = function () {
    // We're not adding it if we're dataOnly or already in dom.
    if (this.options.dataOnly || document.body.contains(this.ui.el)) {
        return this;
    }
    this.options.zone.appendChild(this.ui.el);
    return this;
};

// Remove the Nipple instance from DOM.
Nipple.prototype.removeFromDom = function () {
    if (this.options.dataOnly || !document.body.contains(this.ui.el)) {
        return this;
    }
    this.options.zone.removeChild(this.ui.el);
    return this;
};

// Entirely destroy this nipple
Nipple.prototype.destroy = function () {
    clearTimeout(this.removeTimeout);
    clearTimeout(this.showTimeout);
    clearTimeout(this.restTimeout);
    this.trigger('destroyed', this.instance);
    this.removeFromDom();
    this.off();
};

// Fade in the Nipple instance.
Nipple.prototype.show = function (cb) {
    var self = this;

    if (self.options.dataOnly) {
        return self;
    }

    clearTimeout(self.removeTimeout);
    clearTimeout(self.showTimeout);
    clearTimeout(self.restTimeout);

    self.addToDom();

    self.restCallback();

    setTimeout(function () {
        self.ui.el.style.opacity = 1;
    }, 0);

    self.showTimeout = setTimeout(function () {
        self.trigger('shown', self.instance);
        if (typeof cb === 'function') {
            cb.call(this);
        }
    }, self.options.fadeTime);

    return self;
};

// Fade out the Nipple instance.
Nipple.prototype.hide = function (cb) {
    var self = this;

    if (self.options.dataOnly) {
        return self;
    }

    self.ui.el.style.opacity = self.options.restOpacity;

    clearTimeout(self.removeTimeout);
    clearTimeout(self.showTimeout);
    clearTimeout(self.restTimeout);

    self.removeTimeout = setTimeout(
        function () {
            var display = self.options.mode === 'dynamic' ? 'none' : 'block';
            self.ui.el.style.display = display;
            if (typeof cb === 'function') {
                cb.call(self);
            }

            self.trigger('hidden', self.instance);
        },
        self.options.fadeTime
    );
    if (self.options.restJoystick) {
        self.setPosition(cb, { x: 0, y: 0 });
    }

    return self;
};

// Set the nipple to the specified position
Nipple.prototype.setPosition = function (cb, position) {
    var self = this;
    self.frontPosition = {
        x: position.x,
        y: position.y
    };
    var animTime = self.options.fadeTime + 'ms';

    var transitStyle = {};
    transitStyle.front = u.getTransitionStyle('transition',
        ['top', 'left'], animTime);

    var styles = {front: {}};
    styles.front = {
        left: self.frontPosition.x + 'px',
        top: self.frontPosition.y + 'px'
    };

    self.applyStyles(transitStyle);
    self.applyStyles(styles);

    self.restTimeout = setTimeout(
        function () {
            if (typeof cb === 'function') {
                cb.call(self);
            }
            self.restCallback();
        },
        self.options.fadeTime
    );
};

Nipple.prototype.restCallback = function () {
    var self = this;
    var transitStyle = {};
    transitStyle.front = u.getTransitionStyle('transition', 'none', '');
    self.applyStyles(transitStyle);
    self.trigger('rested', self.instance);
};

Nipple.prototype.resetDirection = function () {
    // Fully rebuild the object to let the iteration possible.
    this.direction = {
        x: false,
        y: false,
        angle: false
    };
};

Nipple.prototype.computeDirection = function (obj) {
    var rAngle = obj.angle.radian;
    var angle45 = Math.PI / 4;
    var angle90 = Math.PI / 2;
    var direction, directionX, directionY;

    // Angular direction
    //     \  UP /
    //      \   /
    // LEFT       RIGHT
    //      /   \
    //     /DOWN \
    //
    if (
        rAngle > angle45 &&
        rAngle < (angle45 * 3) &&
        !obj.lockX
    ) {
        direction = 'up';
    } else if (
        rAngle > -angle45 &&
        rAngle <= angle45 &&
        !obj.lockY
    ) {
        direction = 'left';
    } else if (
        rAngle > (-angle45 * 3) &&
        rAngle <= -angle45 &&
        !obj.lockX
    ) {
        direction = 'down';
    } else if (!obj.lockY) {
        direction = 'right';
    }

    // Plain direction
    //    UP                 |
    // _______               | RIGHT
    //                  LEFT |
    //   DOWN                |
    if (!obj.lockY) {
        if (rAngle > -angle90 && rAngle < angle90) {
            directionX = 'left';
        } else {
            directionX = 'right';
        }
    }

    if (!obj.lockX) {
        if (rAngle > 0) {
            directionY = 'up';
        } else {
            directionY = 'down';
        }
    }

    if (obj.force > this.options.threshold) {
        var oldDirection = {};
        var i;
        for (i in this.direction) {
            if (this.direction.hasOwnProperty(i)) {
                oldDirection[i] = this.direction[i];
            }
        }

        var same = {};

        this.direction = {
            x: directionX,
            y: directionY,
            angle: direction
        };

        obj.direction = this.direction;

        for (i in oldDirection) {
            if (oldDirection[i] === this.direction[i]) {
                same[i] = true;
            }
        }

        // If all 3 directions are the same, we don't trigger anything.
        if (same.x && same.y && same.angle) {
            return obj;
        }

        if (!same.x || !same.y) {
            this.trigger('plain', obj);
        }

        if (!same.x) {
            this.trigger('plain:' + directionX, obj);
        }

        if (!same.y) {
            this.trigger('plain:' + directionY, obj);
        }

        if (!same.angle) {
            this.trigger('dir dir:' + direction, obj);
        }
    } else {
        this.resetDirection();
    }

    return obj;
};

export default Nipple;
