/* 

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

  Copyright (c) 2018
  www.ladyginpro.ru

*/
app.userInteractionLevel = UserInteractionLevel.DONTDISPLAYALERTS;

var scriptName = 'Cropulka',
    settingFile = {
        name: scriptName + '__setting.json',
        folder: Folder.myDocuments + '/LA_AI_Scripts/'
    };

var lockedItems = [],
    hiddenItems = [],
    __offset = 0.2,
    docResolution = '' + activeDocument.rasterEffectSettings.resolution;

function getCustomNumbers ($str, items, returnItems) {
    var __num = $str.replace(/ /g, '').replace(/[^-0-9^,]/gim,'').split(','),
        $maxItems = items.length,
        l = __num.length,
        arr = [];

    function getNumbersBetweenMinMax (min, max) {
        var numbers = [];
        for (var n = min; n <= max; n++) {
            if (n < $maxItems) {
                numbers.push(returnItems ? items[n] : n);
            }
        }
        return numbers;
    }

    for (var j = 0; j < l; j++) {
        if (__num[j].indexOf('-') > -1) {
            var values = __num[j].split('-'),
                min = parseInt(values[0]),
                max = parseInt(values[1]);

            if (!isNaN(min) && !isNaN(max)) arr = arr.concat(getNumbersBetweenMinMax(min - 1, max - 1));
        }
            else {
                var __val = parseInt(__num[j]);
                if (!isNaN(__val) && __val <= $maxItems) {
                    arr.push(returnItems ? items[__val - 1] : __val - 1);
                }
            }
    }

    return arr;
}

function saveLockedHiddenItems (items, childsName) {
    var li = [], hi = [],
        n = items.length;

    while (n--) {
        if (items[n][childsName] && items[n][childsName].length) {
            saveLockedHiddenItems(items[n][childsName], childsName);
        }
            else {
                if (items[n].locked) {
                    li.push(items[n]);
                    items[n].locked = false;
                }
                if (items[n].typename === 'Layer' && !items[n].visible) {
                    hi.push(items[n]);
                    items[n].visible = true;
                }
                    else if (items[n].typename !== 'Layer' && items[n].hidden) {
                        hi.push(items[n]);
                        items[n].hidden = false;
                    }
            }
    }

    lockedItems = lockedItems.concat(li);
    hiddenItems = hiddenItems.concat(hi);
}

function restoreLockedHiddenItems() {
    var i = lockedItems.length,
        j = hiddenItems.length;

    while (i--) lockedItems[i].locked = true;
    while (j--) {
        if (hiddenItems[j].typename === 'Layer') hiddenItems[j].visible = false;
            else hiddenItems[j].hidden = true;
    }
}

function inputNumberEvents (ev, _input, min, max, callback){
    var step,
        _dir = ev.keyName.toLowerCase().slice(0,1),
        _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);
            }
        }
}

var win = new Window('dialog', 'Cropulka \u00A9 www.ladyginpro.ru', undefined);
    win.orientation = 'column';
    win.alignChildren = ['fill', 'fill'];
    
    var globalGroup = win.add('group');
    globalGroup.orientation = 'column';
    globalGroup.margins = 0;
    globalGroup.alignChildren = ['fill', 'fill'];

var panel = globalGroup.add('panel', undefined, 'Artboards');
    panel.orientation = 'column';
    panel.alignChildren = 'fill';
    panel.margins = 20;

var topItemCheckbox = { value: false };
// var topItemCheckbox = panel.add('checkbox', undefined, 'Top item in selection');
//     topItemCheckbox.onClick = function () {
//         groupRadio.enabled = !topItemCheckbox.value;
//         customArts.enabled = (!topItemCheckbox.value ? customArtboardsRadio.value : false);
//     }

var groupRadio = panel.add('group');
    groupRadio.orientation = 'row';

var activeArtboardRadio = groupRadio.add('radiobutton', undefined, 'Active'),
    allArtboardsRadio = groupRadio.add('radiobutton', undefined, 'All'),
    customArtboardsRadio = groupRadio.add('radiobutton', undefined, 'Custom');
    activeArtboardRadio.value = true;
    activeArtboardRadio.onClick = function () { customArts.enabled = false; };
    allArtboardsRadio.onClick = function () { customArts.enabled = false; };
    customArtboardsRadio.onClick = function () { customArts.enabled = true; };

var customArts = panel.add('edittext', undefined, activeDocument.artboards.getActiveArtboardIndex() + 1);
    customArts.enabled = false;

var extraPanel = globalGroup.add('panel', undefined, 'Items: Raster, Placed, Mesh');
    extraPanel.orientation = 'column';
    extraPanel.alignChildren = 'fill';
    extraPanel.margins = [20, 20, 20, 10];

var isRPM = extraPanel.add('checkbox', undefined, 'Enabled crop items');
    isRPM.value = true;
    isRPM.onClick = function (e) {
        RPMGroup.enabled = isRPM.value;
        resGroup.enabled = (maskRPM.value ? false : isRPM.value);
    }

var RPMGroup = extraPanel.add('group');
    RPMGroup.orientation = 'row';

var cropRPM = RPMGroup.add('radiobutton', undefined, 'Crop'),
    maskRPM = RPMGroup.add('radiobutton', undefined, 'Clipping mask');
    cropRPM.value = true;

    cropRPM.onClick = function () { resGroup.enabled = true; }
    maskRPM.onClick = function () { resGroup.enabled = false; }

var resGroup = extraPanel.add('group');
    resGroup.orientation = 'row';

var resolutionList = resGroup.add('dropdownlist', [0, 0, 120, 25], ['Screen (72 ppi)', 'Medium (150 ppi)', 'High (300 ppi)', 'Use Document Raster Effects Resolution', 'Other']),
    resolutionValue = resGroup.add('edittext', [0, 0, 40, 25], docResolution);
    resolutionList.selection = 3;
    resolutionValue.enabled = false;

    function resListItemClick (item) {
        if (item.index === resolutionList.items.length - 1) {
            resolutionValue.enabled = true;
        }
            else {
                resolutionValue.text = (item.index === resolutionList.items.length - 2 ? docResolution : item.text.replace(/[^0-9]/gim,''));
                resolutionValue.enabled = false;
            }
    }
    resolutionList.onChange = function (e) {
        resListItemClick(this.selection);
    }
    resolutionValue.addEventListener('keydown', function (e) { inputNumberEvents(e, this, 16, 5000); });

var includeHLI = globalGroup.add('checkbox', undefined, 'Include hidden & locked items');
    includeHLI.value = true;

var onlySelection = globalGroup.add('checkbox', undefined, 'Crop only the selection');
    onlySelection.onClick = function(e) { panel.enabled = !this.value; }
    onlySelection.value = false;

var winButtons = globalGroup.add('group');
    winButtons.orientation = 'row';
    winButtons.alignChildren = ['fill', 'fill'];

var cancel = winButtons.add('button', [0, 0, 30, 25], 'Cancel');
    cancel.helpTip = 'Press Esc to Close';
    cancel.onClick = function () { win.close(); }

var ok = winButtons.add('button', undefined, 'Run Crop');
    ok.helpTip = 'Press Enter to Run';
    ok.onClick = startAction;
    ok.active = true;

var progressBar = win.add('progressbar', [0, 0, 110, 5]),
    progressBarCounter = 1000;
    progressBar.value = 0;
    progressBar.minvalue = 0;
    progressBar.maxvalue = progressBarCounter;

Object.prototype.extend = function (userObject, deep) {
    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;
};
Object.prototype.toRaster = function (userOptions) {
    var options = new RasterizeOptions(),
    userOptions = userOptions || {};
    var rect = userOptions.rect || undefined;

    options.antiAliasingMethod = (typeof userOptions.antiAliasingMethod === 'string' ? AntiAliasingMethod[userOptions.antiAliasingMethod.toUpperCase()] : AntiAliasingMethod.ARTOPTIMIZED);
    options.extend(userOptions || {}, {
        padding: .0,
        resolution: 72,
        transparency: false,
        clippingMask: false,
        includeLayers: false,
        backgroundBlack: false,
        convertSpotColors: false,
        convertTextToOutlines: false,
    });

    return activeDocument.rasterize(this, rect, options);
};

function cropArtboard (artboardIndex, __progressCounter) {
    try{
    var art, rect, items, topObject,
        isTopObject = (typeof artboardIndex == 'number' ? false : true);

    if (!isTopObject) {
        art = activeDocument.artboards[artboardIndex];
        rect = art.artboardRect;

        if (!onlySelection.value) {
            selection = null;
            activeDocument.artboards.setActiveArtboardIndex(artboardIndex);
            activeDocument.selectObjectsOnActiveArtboard();
        }

        items = selection;
    }
        else if (topObject && topObject.length) {
            topObject = artboardIndex[0];
            items = artboardIndex;
        }
            else {
                return;
            }

    if (!items.length) return;


    var target = items[0].parent,
        rectangle = target.pathItems.rectangle(rect[1], rect[0], rect[2] - rect[0], rect[1] - rect[3]),
        rectangleBounds = rectangle.geometricBounds,
        counterLocal = {
            items: 0,
            image: 0,
        };
        rectangle.opacity = 0;

    // check position item on artboard
    function checkPosition ($item, coords) {
        return (
            ($item.visibleBounds[0] >= coords[0] - __offset) &&
            ($item.visibleBounds[1] <= coords[1] + __offset) &&
            ($item.visibleBounds[2] <= coords[2] + __offset) &&
            ($item.visibleBounds[3] >= coords[3] - __offset)
        );
    }
    function isOutside ($item, coords) {
        return (
            ($item.visibleBounds[0] > coords[2] + __offset) ||
            ($item.visibleBounds[1] < coords[3] - __offset) ||
            ($item.visibleBounds[2] < coords[0] - __offset) ||
            ($item.visibleBounds[3] > coords[1] + __offset)
        );
    }

    // raster and mesh
    function getRasterRect (item, __rect) {
        selection = null;
    
        var bnds = item.geometricBounds,
            __target = item.parent,
            __maskRect = __target.pathItems.rectangle(__rect[1], __rect[0], __rect[2] - __rect[0], __rect[1] - __rect[3]),
            __maskItem = __target.pathItems.add(),
            __maskGroup = __target.groupItems.add();
        __maskRect.opacity = 0;
    
        __maskItem.setEntirePath([
            [bnds[0], bnds[1]],
            [bnds[2], bnds[1]],
            [bnds[2], bnds[3]],
            [bnds[0], bnds[3]]
        ]);
        __maskItem.filled = true;
        __maskItem.closed = true;
    
        __maskItem.moveToBeginning(__maskGroup);
        __maskRect.moveToBeginning(__maskGroup);
    
        __maskGroup.selected = true;
        app.executeMenuCommand('Live Pathfinder Crop');
        app.executeMenuCommand('expandStyle');
    
        var data = selection[0].geometricBounds;

        if (maskRPM.value) {
            ungroupResult(selection);
            return selection[0];
        }
            else {
                selection[0].remove();
                return data;
            }
    }
    
    function cropImageAndMesh (imageMesh, __rect, ignoreCoordinates, notCount) {
        var j = imageMesh.length;
    
        while (j--) {
            if (imageMesh[j].typename === 'GroupItem') {
                cropImageAndMesh(imageMesh[j].pageItems, __rect, true, true);
            }
                else if (
                    !(/* ignoreCoordinates && */ checkPosition(imageMesh[j], __rect)) &&
                    ((imageMesh[j].typename === 'PlacedItem')
                    || (imageMesh[j].typename === 'RasterItem')
                    || (imageMesh[j].typename === 'MeshItem'))
                ) {
                    var $rect = getRasterRect(imageMesh[j], __rect),
                        blendMode = imageMesh[j].blendingMode;

                    if (maskRPM.value) {
                        var CMGroup = $rect.parent.groupItems.add();
                        CMGroup.moveBefore(imageMesh[j]);
                        imageMesh[j].moveToBeginning(CMGroup);
                        $rect.moveToBeginning(CMGroup);
                        CMGroup.clipped = true;
                        $rect.clipping = true;
                    }
                        else {
                            imageMesh[j].toRaster({
                                resolution: 300,
                                rect: $rect,
                                transparency: true
                            }).blendingMode = blendMode;
                        }
                }

            if (!notCount) {
                progressBar.value += counterLocal.image;
                win.update();
            }
        }
    }

    // other items
    function cropProcessBefore (__items, ignoreCoordinates, notCount) {
        var i = __items.length;
        while (i--) {
            if (isOutside(__items[i], rect)) {
                __items[i].remove();
            }
                else if (
                    !(/* ignoreCoordinates && */ checkPosition(__items[i], rect))
                    && (
                        (__items[i].typename !== 'PlacedItem') &&
                        (__items[i].typename !== 'RasterItem') &&
                        (__items[i].typename !== 'MeshItem')
                    )
                ) {
                    if (__items[i].typename === 'GroupItem') {
                        cropProcessBefore(__items[i].pageItems, true, true);
                    }
                        else if (!__items[i].guides) {
                            cropProcess(__items[i]);
                        }
                }

            if (!notCount) {
                progressBar.value += counterLocal.items;
                win.update();
            }
        }
    }

    function ungroupResult (__items) {
        var l = __items.length;
        
        for (var i = 0; i < l; i++) {
            if (__items[i].typename === 'GroupItem') {
                var j = __items[i].pageItems.length;
                while (j--) { __items[i].pageItems[0].moveBefore(__items[i]); }
                __items[i].remove();
            } 
        }
    }

    function cropProcess (item) {
        var __target = item.parent,
            __group = __target.groupItems.add(),
            __rectangle = rectangle.duplicate(),
            blendMode = item.blendingMode,
            $opacity = item.opacity;

        __group.move(item, ElementPlacement.PLACEAFTER);
        item.moveToBeginning(__group);
        __rectangle.moveToBeginning(__group);
        __rectangle.filled = true;
        __rectangle.stroked = false;
        __rectangle.clipping = true;
        __group.clipped = true;

        selection = null;
        __group.selected = true;
    
        app.executeMenuCommand('Live Pathfinder Crop');
        app.executeMenuCommand('expandStyle');

        var i = selection.length;
        if (i > 0) while (i--) {
            if (selection[i].typename === 'GroupItem') {
                var j = selection[i].pageItems.length;
                if (j > 0) while (j--) {
                    selection[i].pageItems[j].blendingMode = blendMode;
                    selection[i].pageItems[j].opacity = $opacity;
                }
            }
        }

        // ungroup
        ungroupResult(selection);
    }

    // run crop items
    counterLocal.items = __progressCounter / items.length;
    cropProcessBefore(items);

    // run crop image and mesh

    if (isRPM.value) {
        if (!isTopObject) {
            selection = null;
            activeDocument.selectObjectsOnActiveArtboard();
            counterLocal.image = __progressCounter / selection.length;
            cropImageAndMesh(selection, rectangleBounds);
        }
            else {
                cropImageAndMesh(items, rectangleBounds);
                topObject.remove();
            }
    }

    rectangle.remove();
    }catch(err) {alert(err + '\n' + err.line);}
}

function startAction() {
    var __arts = activeDocument.artboards,
        __artsLength = __arts.length,
        __indexs = [],
        progressBarCounter = progressBar.maxvalue,
        progressBarCounterExtra = (isRPM.value ? 2 : 1);

    if (!onlySelection.value) {
        selection = null;
        app.redraw();
    }

    if (!topItemCheckbox.value) {
        if (activeArtboardRadio.value || onlySelection.value) {
            __indexs.push(__arts.getActiveArtboardIndex());
        }
            else {
                __indexs = __indexs.concat(getCustomNumbers( (allArtboardsRadio.value ? ('1-' + __artsLength) : customArts.text), __arts));
            }
    
        progressBarCounter /= __indexs.length;
    }
        else {
            progressBarCounter /= selection.length;
        }

    globalGroup.enabled = false;

    if (includeHLI.value) {
        saveLockedHiddenItems(activeDocument.layers, 'layers');
        saveLockedHiddenItems(activeDocument.pageItems, 'pageItems');
    }

    if (!topItemCheckbox.value) {
        for (var x = 0; x < __indexs.length; x++) {
            cropArtboard(__indexs[x], progressBarCounter / progressBarCounterExtra);
        }
    }
        else {
            cropArtboard(selection, progressBarCounter / progressBarCounterExtra);
        }

    if (includeHLI.value) {
        restoreLockedHiddenItems();
    }

    win.close();
}

function saveSettings() {
    var $file = new File(settingFile.folder + settingFile.name),
        data = [
            activeArtboardRadio.value,
            allArtboardsRadio.value,
            customArtboardsRadio.value,
            customArts.text,
            extraPanel.value,
            isRPM.value,
            cropRPM.value,
            maskRPM.value,
            resolutionList.selection.index,
            resolutionValue.text,
            includeHLI.value,
            onlySelection.value
        ].toString().replace(/,/g, '@');

    $file.open('w');
    $file.write(data);
    $file.close();
}

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('@');
                activeArtboardRadio.value = ($main[0] === 'true');
                allArtboardsRadio.value = ($main[1] === 'true');
                customArtboardsRadio.value = ($main[2] === 'true');
                customArts.text = $main[3];
                extraPanel.value = ($main[4] === 'true');
                isRPM.value = ($main[5] === 'true');
                cropRPM.value = ($main[6] === 'true');
                maskRPM.value = ($main[7] === 'true');
                resolutionList.selection = parseInt($main[8]);
                resolutionValue.text = $main[9];
                includeHLI.value = ($main[10] === 'true');
                onlySelection.value = ($main[11] === 'true');

                panel.enabled = !onlySelection.value;
                RPMGroup.enabled = isRPM.value;
                resGroup.enabled = (maskRPM.value ? false : isRPM.value);
                customArts.enabled = customArtboardsRadio.value;
                resolutionValue.enabled = (resolutionList.selection.index === resolutionList.items.length - 1);
                resolutionValue.text = (resolutionList.selection.index === resolutionList.items.length - 2 ? docResolution : (resolutionList.selection.index === resolutionList.items.length - 1 ? resolutionValue.text : resolutionList.selection.text.replace(/[^0-9]/gim,'')));
        } catch (e) {}
        $file.close();
    }
}

win.onClose = function() {
    saveSettings();
    return true;
}

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

checkSettingFolder();
loadSettings();
win.center();
win.show();