/* 

  Author: Alexander Ladygin (i@ladygin.pro)
  Program version: Adobe Illustrator CC+ (presumably in Adobe Illustrator CS6 - did not test)
  Name: circular.jsx;

  Copyright (c) 2018
  www.ladyginpro.ru

*/

$.errorMessage = function (err) {alert(err + '\n' + err.line);};
Object.prototype.extend = function (userObject, deep) {try {for (var key in userObject) {if (this.hasOwnProperty(key)) {if (deep&& this[key] instanceof Object&& !(this[key] instanceof Array)&& userObject[key] instanceof Object&& !(userObject[key] instanceof Array)) {this[key].extend(userObject[key], deep);}else this[key] = userObject[key];}}return this;}catch (e) {$.errorMessage('$.objectParser() - error: ' + e);}};
$.getBounds = function (arr, bounds) {bounds = bounds || 'geometricBounds';bounds = (bounds && bounds.toLowerCase().indexOf('bounds') === -1) ? bounds += 'Bounds' : bounds;var x = [], y = [], w = [], h = [];for (var i = 0; i < arr.length; i++) {x.push(arr[i][bounds][0]);y.push(arr[i][bounds][1]);w.push(arr[i][bounds][2]);h.push(arr[i][bounds][3]);};return [Math.min.apply(null, x), Math.max.apply(null, y), Math.max.apply(null, w), Math.min.apply(null, h)];};
$.toArr = function (classCollection, callback) {var arr = [], l = classCollection.length;if (l > 0) {for (var i = 0; i < l; i++) {arr.push(classCollection[i]);}}if (callback instanceof Function) return callback(arr);return arr;}
$.getUnits = function (val, def) {return 'px,pt,mm,cm,in,pc'.indexOf(val.slice(-2)) > -1 ? val.slice(-2) : def;};
$.convertUnits = function (obj, b) {if (obj === undefined) {return obj;}if (b === undefined) {b = 'px';}if (typeof obj === 'number') {obj = obj + 'px';}if (typeof obj === 'string') {var unit = $.getUnits(obj),val = parseFloat(obj);if (unit && !isNaN(val)) {obj = val;}else if (!isNaN(val)) {obj = val; unit = 'px';}}if (((unit === 'px') || (unit === 'pt')) && (b === 'mm')) {obj = parseFloat(obj) / 2.83464566929134;}else if (((unit === 'px') || (unit === 'pt')) && (b === 'cm')) {obj = parseFloat(obj) / (2.83464566929134 * 10);}else if (((unit === 'px') || (unit === 'pt')) && (b === 'in')) {obj = parseFloat(obj) / 72;}else if ((unit === 'mm') && ((b === 'px') || (b === 'pt'))) {obj = parseFloat(obj) * 2.83464566929134;}else if ((unit === 'mm') && (b === 'cm')) {obj = parseFloat(obj) * 10;}else if ((unit === 'mm') && (b === 'in')) {obj = parseFloat(obj) / 25.4;}else if ((unit === 'cm') && ((b === 'px') || (b === 'pt'))) {obj = parseFloat(obj) * 2.83464566929134 * 10;}else if ((unit === 'cm') && (b === 'mm')) {obj = parseFloat(obj) / 10;}else if ((unit === 'cm') && (b === 'in')) {obj = parseFloat(obj) * 2.54;}else if ((unit === 'in') && ((b === 'px') || (b === 'pt'))) {obj = parseFloat(obj) * 72;}else if ((unit === 'in') && (b === 'mm')) {obj = parseFloat(obj) * 25.4;}else if ((unit === 'in') && (b === 'cm')) {obj = parseFloat(obj) * 25.4;}return parseFloat(obj);};

var isUndo = false,
    scriptName = 'Circular',
    copyright = ' \u00A9 www.ladyginpro.ru',
    settingFile = {
        name: scriptName + '__setting.json',
        folder: Folder.myDocuments + '/LA_AI_Scripts/'
    },
    $items = selection;

function ungroup (group) {
    var i = group.pageItems.length;
    if (i > 0) while (i--) group.pageItems[i].moveBefore(group);
    group.remove();
}

function rvbn (min, max) {
    // random value between numbers 
    return min + Math.floor(Math.random() * (max - min));
}

function circular (userOptions) {
    var options = {
        copies: 7,
        offset: 200,
        offsetRandom: false,
        offsetMin: '0 px',
        offsetMax: '0 px',
        angleStart: 0,
        angleEnd:  360,
        notCopies: true,
        resetItem: true,
        bounds: 'visible',
        direction: 'down',
        isRotateItem: false,
        rotateItemAngle: 45,
        position: 'relative',
        counterclockwise: false,
    }.extend(userOptions || {});

    var CCW = !options.counterclockwise ? -1 : 1,
        TFP = options.direction.toUpperCase().replace('UP', 'TOP').replace('DOWN', 'BOTTOM');
    options.bounds = (options.bounds.slice(0,1).toLowerCase() === 'v' ? 'visibleBounds' : 'geometricBounds');
    options.direction = options.direction.toLowerCase().slice(0,1);
    options.copies = (options.copies > 0 ? options.copies : 1);
    options.offset = $.convertUnits(options.offset, 'px');
    options.offsetMin = $.convertUnits(options.offsetMin, 'px');
    options.offsetMax = $.convertUnits(options.offsetMax, 'px');

    var sbnds = $.getBounds($items, options.bounds),
        cpos = {
            x: sbnds[0] + (sbnds[2] - sbnds[0]) / 2,
            y: sbnds[1] - (sbnds[1] - sbnds[3]) / 2,
        };

    if (options.notCopies) {
        var items = $items, l = items.length,
            aVal = (options.angleEnd - options.angleStart) / (l > 1 ? (options.angleEnd >= 350 ? l : l - 1) : l);
            // aVal = (options.angleEnd - options.angleStart) / (l - 1 > 0 ? l - 1 : l);

        for (var i = 0; i < l; i++) {
            toRotate(items[i], options.angleStart + aVal * i, cpos);
        }
    }
        else {
            var items = $items, l = items.length,
                aVal = (options.angleEnd - options.angleStart) / options.copies;
            for (var i = 0; i < l; i++) {
                for (var j = 0; j < options.copies; j++) {
                    toRotate(items[i], options.angleStart + aVal * j);
                }
                // items[i].remove();
            }
        }

    function toRotate (item, angle, $pos) {
        angle *= CCW;

        var $offset = (options.offsetRandom ? rvbn(options.offsetMin, options.offsetMax) : options.offset),
            bnds = item[options.bounds],
            $w = bnds[2] - bnds[0],
            $h = bnds[1] - bnds[3];

        if (options.position.toLowerCase().slice(0,1) === 'r') $offset += ($w >= $h ? $w : $h);

        // align center
        if (options.notCopies) {
            item.position = [$pos.x - $w / 2, $pos.y + $h / 2];
            bnds = item[options.bounds];
        }

        var centerPointSize = 0,
            $group = item.parent.groupItems.add(),
            node = (options.notCopies ? item : item.duplicate($group, ElementPlacement.INSIDE)),
            centerPoint = $group.pathItems.ellipse(bnds[1] - $h / 2 + centerPointSize / 2, bnds[0] + $w / 2 - centerPointSize / 2, centerPointSize, centerPointSize);

        // move items
        $group.moveBefore(item);
        if (options.notCopies) node.moveToBeginning($group);

        // set $offset
        // node.position = [node.position[0], node.position[1] - $offset];
        if (options.direction === 'u') node.top -= $offset;
            else if (options.direction === 'r') node.left -= $offset;
            else if (options.direction === 'd') node.top += $offset;
            else if (options.direction === 'l') node.left += $offset;

        // rotate item
        $group.rotate(angle, true, true, true, true, Transformation[TFP]);
    
        // reset rotate from the item
        if (options.resetItem) node.rotate(angle * -1, true, true, true, true, Transformation.CENTER);
        // rotate only item
        if (options.isRotateItem) node.rotate((__randomRotate.value ? Math.floor(Math.random() * 360) : options.rotateItemAngle * CCW), true, true, true, true, Transformation.CENTER);
    
        centerPoint.remove();
        ungroup($group);

        return item;
    }
    
}

function inputNumberEvents (ev, _input, min, max, callback){
    var step,
        _dir = (ev.keyName ? ev.keyName.toLowerCase().slice(0,1) : '#none#'),
        _value = parseFloat(_input.text),
        units = (',px,pt,mm,cm,in,'.indexOf(_input.text.length > 2 ? (',' + _input.text.replace(/ /g, '').slice(-2) + ',') : ',!,') > -1 ? _input.text.replace(/ /g, '').slice(-2) : '');

    min = (min === undefined ? 0 : min);
    max = (max === undefined ? Infinity : max);
    step = (ev.shiftKey ? 10 : (ev.ctrlKey ? .1 : 1));

    if (isNaN(_value)) {
        _input.text = min;
    }
        else {
            _value = ( ((_dir === 'u') || (_dir === 'r')) ? _value + step : (((_dir === 'd') || (_dir === 'l')) ? _value - step : false) );
            if (_value !== false) {
                _value = (_value <= min ? min : (_value >= max ? max : _value))
                _input.text = _value;
                if (callback instanceof Function) callback(_value, _input, min, max, units);
                    else if (units) _input.text = parseFloat(_input.text) + ' ' + units;
            }
                else if (units) _input.text = parseFloat(_input.text) + ' ' + units;
        }
}

function normalizeAngle (val, item, min, max) {
    if (item === __startAngle) {
        if (val > parseFloat(__endAngle.text)) { __endAngleSlider.value = __endAngle.text = ((val + 1) >= max ? max : val + 1); }
    }
        else if (item === __endAngle) {
            if (val < parseFloat(__startAngle.text)) { __startAngleSlider.value = __startAngle.text = ((val - 1) <= min ? min - 1 : val - 1); }
        }
}

function normalizeOffset (val, item, min, max, units) {
    if (item === __offsetMin) {
        if (val > $.convertUnits(__offsetMax.text, 'px')) {
            __offsetMax.text = ((val + 1) >= max ? max : val + 1) + (units ? ' ' + units : '');
        }
    }
        else if (item === __offsetMax) {
            if (val < $.convertUnits(__offsetMin.text, 'px')) {
                __offsetMin.text = ((val - 1) <= min ? min - 1 : val - 1) + (units ? ' ' + units : '');
            }
        }

    if (units) item.text += ' ' + units;
}

var win = new Window('dialog', scriptName + copyright);
win.alignChildren = 'fill';

with (panel = win.add('panel')) {
    alignChildren = ['fill', 'bottom'];

    with (add('group')) {
        orientation = 'row';

        add('statictext', [0, 0, 80, 25], 'Start angle:').justify = 'center';
        var __startAngleSlider = add('slider', [0, 0, 170, 15], 0, 0, 359),
            __startAngle = add('edittext', [0, 0, 50, 25], 0);
        __startAngleSlider.onChanging = function (e) { __startAngle.text = Math.round(this.value); normalizeAngle(Math.round(this.value), __startAngle, 1, 360); }
        __startAngleSlider.onChange = function (e) { previewStart(); }
        __startAngle.addEventListener('keydown', function (e) { inputNumberEvents(e, this, 0, 359); __startAngleSlider.value = Math.round(this.text); });
        __startAngle.addEventListener('keyup', function (e) { normalizeAngle(this.value, __startAngle, 1, 360); previewStart(); });
    }
    with (add('group')) {
        orientation = 'row';

        var __isEndAngle = add('checkbox', [0, 0, 80, 25], 'End angle:'),
            __endAngleSlider = add('slider', [0, 0, 170, 15], 360, 1, 360),
            __endAngle = add('edittext', [0, 0, 50, 25], 360);
        __isEndAngle.alignment = 'bottom';
        __endAngleSlider.onChanging = function (e) { __endAngle.text = Math.round(this.value); normalizeAngle(Math.round(this.value), __endAngle, 1, 360); }
        __endAngleSlider.onChange = function (e) { previewStart(); }
        __endAngle.addEventListener('keydown', function (e) { inputNumberEvents(e, this, 1, 360); __endAngleSlider.value = Math.round(this.text); });
        __endAngle.addEventListener('keyup', function (e) { normalizeAngle(this.value, __endAngle, 1, 360); previewStart(); });

        __isEndAngle.onClick = function () { __endAngleSlider.enabled = __endAngle.enabled = this.value; previewStart(); }
        __endAngleSlider.enabled = __endAngle.enabled = false;
    }
    with (add('group')) {
        orientation = 'row';

        var __isRotateItem = add('checkbox', undefined, 'Rotate each items'),
            __randomRotate = add('checkbox', undefined, 'Rotate each items Random');

        __isRotateItem.onClick = function () {
            __randomRotate.enabled = this.value;
            __rotateItemSlider.enabled = __rotateItemAngle.enabled = (this.value && __randomRotate.value ? false : this.value);
            previewStart();
        }
        __randomRotate.onClick = function () { __rotateItemSlider.enabled = __rotateItemAngle.enabled = !this.value; previewStart(); }
    }
    with (add('group')) {
        var __rotateItemSlider = add('slider', [0, 0, 260, 15], 360, 0, 360),
            __rotateItemAngle = add('edittext', [0, 0, 50, 25], 360);

        __rotateItemSlider.onChanging = function (e) { __rotateItemAngle.text = Math.round(this.value); normalizeAngle(Math.round(this.value), __rotateItemAngle, 0, 360); }
        __rotateItemSlider.onChange = function (e) { previewStart(); }
        __rotateItemAngle.addEventListener('keydown', function (e) { inputNumberEvents(e, this, 0, 360); __rotateItemSlider.value = Math.round(this.text); });
        __rotateItemAngle.addEventListener('keyup', function (e) { previewStart(); });
        __rotateItemSlider.enabled = __rotateItemAngle.enabled = false;
    }
    with (add('group')) {
        orientation = 'row';
        alignChildren = ['fill', 'fill'];

        add('statictext', undefined, 'Offset:');
        var __offset = add('edittext', [0, 0, 100, 25], '0 px'),
            __direction = add('dropdownlist', undefined, 'Direction: Up,Direction: Right,Direction: Down,Direction: Left'.split(','));
        __direction.selection = 0;
        __offset.addEventListener('keydown', function (e) { inputNumberEvents(e, this, -Infinity, Infinity); });
        __offset.addEventListener('keyup', function (e) { previewStart(); });
        __direction.onChange = function () { previewStart(); }
    }
    with (add('group')) {
        orientation = 'row';
        alignChildren = ['fill', 'fill'];

        // add('statictext', undefined, 'Offset:');
        var __randomOffset = add('checkbox', undefined, 'Random offset');
        __randomOffset.alignment = 'bottom';
        add('statictext', undefined, 'Min:');
        var __offsetMin = add('edittext', [0, 0, 60, 25], '0 px');
        add('statictext', undefined, 'Max:');
        var __offsetMax = add('edittext', [0, 0, 60, 25], '0 px');

        __offsetMin.enabled = __offsetMax.enabled = __randomOffset.value;

        __randomOffset.onClick = function () {
            __offsetMin.enabled = __offsetMax.enabled = this.value;
            __offset.enabled = !this.value;
            previewStart();
        }
        __offsetMin.addEventListener('keydown', function (e) { inputNumberEvents(e, __offsetMin, 0, Infinity, normalizeOffset); });
        __offsetMin.addEventListener('change', function (e) { normalizeOffset($.convertUnits(this.text, 'px'), __offsetMin, 0, Infinity); });
        __offsetMin.addEventListener('keyup', function (e) { previewStart(); });
        __offsetMax.addEventListener('keydown', function (e) { inputNumberEvents(e, __offsetMax, 0, Infinity, normalizeOffset); });
        __offsetMax.addEventListener('change', function (e) { normalizeOffset($.convertUnits(this.text, 'px'), __offsetMax, 0, Infinity); });
        __offsetMax.addEventListener('keyup', function (e) { previewStart(); });
    }

    __offset.enabled = !__randomOffset.value;

    with (add('group')) {
        orientation = 'row';
        alignChildren = ['fill', 'fill'];

        var __pos = add('dropdownlist', [0, 0, 105, 25], 'Position: Absolute,Position: Relative'.split(','));
        __pos.selection = 1;
        __pos.onChange = function () { previewStart(); }
        var __bounds = add('dropdownlist', [0, 0, 105, 25], 'Bounds: Geometric,Bounds: Visible'.split(','));
        __bounds.selection = 0;
        __bounds.onChange = function () { previewStart(); }
    }
    with (add('group')) {
        orientation = 'row';
        alignChildren = 'left';

        var __counterClockwise = add('checkbox', undefined, 'Counter Clockwise');
        __counterClockwise.onClick = function () { previewStart(); }
        var __notRotate = add('checkbox', undefined, 'Reset rotate from item');
        __notRotate.onClick = function () { previewStart(); }
    }
    with (add('group')) {
        orientation = 'row';
        alignChildren = 'left';

        var __copiesEnabled = add('checkbox', undefined, 'Copies enabled');
        __copiesEnabled.onClick = function () { __copies.enabled = this.value; previewStart(); }
        var __copies = add('edittext', [0, 0, 200, 25], 1);
        __copies.enabled = false;
        __copies.addEventListener('keydown', function (e) { inputNumberEvents(e, this, 1, Infinity); this.text = parseInt(this.text); });
        __copies.addEventListener('keyup', function (e) { previewStart(); });
        __copies.onChange = function () { if (isNaN(parseInt(this.text))) this.text = 1; }
    }
}

with (win.add('group')) {
    orientation = 'row';
    alignChildren = ['fill', 'fill'];

    var preview = add('checkbox', undefined, 'Preview'),
        cancelBtn = add('button', undefined, 'Cancel'),
        applyBtn = add('button', undefined, 'OK');

    preview.onClick = function() { previewStart(); }
    cancelBtn.onClick = function() { win.close(); }
    applyBtn.onClick = function() {
        if (preview.value && isUndo) {
            isUndo = false;
            win.close();
        }
            else {
                app.undo();
                startAction();
                isUndo = false;
                win.close();
            }
    }
}

function getData() {
    return {
        copies: parseInt(__copies.text),
        offset: __offset.text,
        angleStart: parseInt(__startAngle.text),
        angleEnd:  (!__isEndAngle.value ? 360 : parseInt(__endAngle.text)),
        notCopies: !__copiesEnabled.value,
        resetItem: __notRotate.value,
        bounds: __bounds.selection.text.toLowerCase().replace('bounds: ', ''),
        direction: __direction.selection.text.toLowerCase().replace('direction: ', ''),
        position: __pos.selection.text.toLowerCase().replace('position: ', ''),
        counterclockwise: __counterClockwise.value,
        isRotateItem: __isRotateItem.value,
        rotateItemAngle: __rotateItemSlider.value,
        offsetRandom: __randomOffset.value,
        offsetMin: __offsetMin.text,
        offsetMax: __offsetMax.text,
    };
}


function startAction() {
    circular(getData());
}

function previewStart() {
    if (preview.value) {
        if (isUndo) app.undo();
            else isUndo = true;

        startAction();
        app.redraw();
    }
        else if (isUndo) {
            app.undo();
            app.redraw();
            isUndo = false;
        }
}

win.onClose = function () {
    if (isUndo) {
        app.undo();
        app.redraw();
        isUndo = false;
    }

    // selection = $items;
    saveSettings();
    return true;
}

function saveSettings() {
    try{
    var $file = new File(settingFile.folder + settingFile.name),
        data = [
            __startAngleSlider.value,
            __startAngle.text,
            __isEndAngle.value,
            __endAngleSlider.value,
            __endAngle.text,
            __offset.text,
            __direction.selection.index,
            __pos.selection.index,
            __bounds.selection.index,
            __counterClockwise.value,
            __notRotate.value,
            __copiesEnabled.value,
            __copies.text,
            __isRotateItem.value,
            __rotateItemSlider.value,
            __rotateItemAngle.text,
            __randomRotate.value,
            __randomOffset.value,
            __offsetMin.text,
            __offsetMax.text
        ].toString();

    $file.open('w');
    $file.write(data);
    $file.close();
    }catch(e){$.errorMessage(e);}
}

function loadSettings() {
    var $file = File(settingFile.folder + settingFile.name);
    if ($file.exists) {
        try {
            $file.open('r');
            var data = $file.read().split('\n'),
                $main = data[0].split(',');
            __startAngleSlider.value = parseInt($main[0]);
            __startAngle.text = $main[1];
            __isEndAngle.value = ($main[2] === 'true');
            __endAngleSlider.value = parseInt($main[3]);
            __endAngle.text =  $main[4];
            __offset.text =  $main[5];
            __direction.selection.index = parseInt($main[6]);
            __pos.selection.index = parseInt($main[7]);
            __bounds.selection.index = parseInt($main[8])
            __counterClockwise.value = ($main[9] === 'true');
            __notRotate.value = ($main[10] === 'true');
            __copiesEnabled.value = ($main[11] === 'true');
            __copies.text = $main[12];
            __isRotateItem.value = ($main[13] === 'true');
            __rotateItemSlider.value = parseInt($main[14]);
            __rotateItemAngle.text = $main[15];
            __randomRotate.value = ($main[16] === 'true');
            if ($main[17]) __randomOffset.value = ($main[17] === 'true');
            if ($main[18]) __offsetMin.text = $main[18];
            if ($main[19]) __offsetMax.text = $main[19];

            __copies.enabled = __copiesEnabled.value;
            __endAngleSlider.enabled = __endAngle.enabled = __isEndAngle.value;
            __randomRotate.enabled = __isRotateItem.value;
            __rotateItemSlider.enabled = __rotateItemAngle.enabled = (__isRotateItem.value && __randomRotate.value ? false : __isRotateItem.value);

            __offset.enabled = !__randomOffset.value;
            __offsetMin.enabled = __offsetMax.enabled = __randomOffset.value;
        } catch (e) {}
        $file.close();
    }
}

function checkSettingFolder() {
    var $folder = new Folder(settingFile.folder);
    if (!$folder.exists) $folder.create();
}

checkSettingFolder();
loadSettings();

selection = null;
app.redraw();

win.center();
win.show();