﻿

function layerByName(layers,name)
{
    var i;
    for(i = 0; i < layers.length; i++)
    {
        var layer = layers[i];
        if(layer.name == name)
            return layer;
    }
    return null;
}


function newLayer(doc,baseName)
{
	var n = 0;
	var result = null;
	while(result == null)
	{
		n++;
		var aName = "" + baseName + n;
		var existingLayer = layerByName(doc.layers,aName);
		if(existingLayer == null)
		{
			result = doc.layers.add();
			result.name = aName;
		}
	}
	return result;
}

function addP(points,x,y)
{
	var p = points.add();
	p.anchor = [x,y];
	p.leftDirection = p.anchor;
	p.rightDirection = p.anchor;
}


var pattern=
"abccccccddeeeee/" +
"abbbbaddddbaaeb/" +
"aaccbaaaabbaaab/" +
"acccdaccbbbabbb/" +
"acbddddcdddcccb/" +
"bbbdacccdddcacc/" +
"bcbaa.....eaaaa/" +
"cccaa.....ebbac/" +
"cbbad.....ebccc/" +
"cbddddaaeeebcab/" +
"bbcbdaaaaccbcab/" +
"bccbbddcccdbaab/" +
"ccbbddabbcddabb/" +
"cabadaaabbdcabc/" +
"aaaadaabbddcccc";

//~ pattern = "aaab/aabb";


apattern="jjjjppppuuuuzzzzffffllllvvvvmmccssssmmmmiiiihhhh/" +
"jjjzppphuuukzzzqfffalllcvvvmmmcccsssbmmmviiiqhhh/" +
"kjzzzphhhukkkzqqqfaaalcccvffmmcchhsbbbmvvviqqqhg/" +
"kkzzzzhhhhkkkkqqqqaaaaccccfffmchhhbbbbvvvvqqqqgg/" +
"kkkeqqddddggggkkkkbbbbddddffrrrrhhwwwwtttthhvggg/" +
"kkeeqqqdddpgggvkkkbbbpdddxflrrrbbhwwwgttthhhvvgg/" +
"leeeqqjjdpppgvvvkggbpppdxxxllrbbbrrwgggtzzhhvvvf/" +
"lleeqjjjppppvvvvgggppppxxxxlllqbbrrrggggzzzhvvff/" +
"lllfrrjjbbbbaaaalggqqqqwwwwllqqgbrrvllllzzggufff/" +
"llffrrrjbbblaaalllgvqqqzwwweqqqggrvvvlllzggguuff/" +
"mfffrrkkcblllallllvvvqzzzweeeqqgggvvvvlssfgguuue/" +
"mmffrkkkccllllwwhhvvvvzzzzeeeewgglaaaasssffguuee/" +
"mmmgwwkkcccqbwwwhhhcmffffbbbbwwwlllaaakssfffpeee/" +
"mmggwwwkccqqbbwwhhccmmfffbbbwwwwllllakkksffpppee/" +
"ngggwwxeeqqqbbbwhcccmmmfoobhvvvvqqqqkkkkbbppppdd/" +
"nnggwxxeeeqqbbrrrrccmmgooohhhvvvfqqqffffbbbooddd/" +
"nnnaxxxeerrrrcrrrwwwwgggoohhhhvfffqufffjbbaooodd/" +
"nnaaaxxevrrrcccrmmwwwggggoggggffffuuufjjbaaooccd/" +
"ooaaaallvvrccccmmmxwnnnnuugggaaaauuuujjjaaaolccc/" +
"oooyylllvvvmxxxxmmxxnnnhuuugkkaaakzzzzjjyaallccb/" +
"oopyyyllvvmmmxxxnmxxxnhhuuakkkzakkkzzziiyylllcbb/" +
"oppyynnlwwmmmmxnnnxxthhhuaaakkzzkkkkziiiyyyllbbb/" +
"pppybnnnwwwhhhhnnnnttthhaaaajkzzzppppeiiyynnnnbb/" +
"qppbbnnowwshhhssssttttbttttjjjzzepppeeeirkknnnaa/" +
"qqbbbnoowssshdsssiuuuubbtttjjjjeeepeeeerrkkknaaa/" +
"qqqbbooossssdddsiiiuuubbbtzzppeeeeooddrrrkkjttaa/" +
"qqccccooxxxxddddiiiiuobbszzzpppujooodddrrkjjttta/" +
"rrcccssfxxxnyyyyjjjjooossszzppuujjooddppxjjjttzz/" +
"rrrcsssffxnnnyyyjjjoooosssszpuuujjjodpppxxjjtzzz/" +
"rrhhtssfffnnnnyttjzzzzrrrriiiiuujjyyyyppxxxesszz/" +
"rhhhttsffyiiiitttddzzzjrrrdiiiyyyynyyyopxxeesssz/" +
"sshhtttmyyyiiiettdddzjjjrdddityyynnnyoooweeessyy/" +
"ssshttmmyyyyieeotddyyjjjjddddttynnnnoooowweesyyy/" +
"ssiiummmtttteeeoodyyykkkkcccctttiiiiccccwwwdrryy/" +
"siiiuummgtttjeeoooeyykkkqcccottxiiitcccqwwddrrry/" +
"ttiiuuugggtjjjuooeeeyskqqqcoooxxxitttcqqqdddrrxx/" +
"tttiuuggggjjjjuueeeessqqqqooooxxxxttttqqqqddrxxx/" +
"ttddddiiiioooouuupzsssppppnnnnddddxxxxuuuummmmxx/" +
"tudddviiizooofuuppzzssipppynnnsdddmxxxhuuucmmmwx/" +
"uuudvvvizzzofffpppzzziiipyyynsssdmmmxhhhucccmwww/" +
"uuuuvvvvzzzzffffppzziiiiyyyyssssmmmmhhhhccccwwww";

var g = {};


function scanPattern() // break main string apart, and notate g.x g.y extents.
{
    g.parts = pattern.split("/");
    g.x = 0;
    g.y = g.parts.length;
    
    g.segments = []; // unit segments segment.[p0, p1].[x,y]
    g.segmentRuns = []; // each run is an array of points
    
    // find the widest
    for(var ix = 0; ix < g.y; ix++)
    {
        var w = g.parts[ix].length;
        if(w > g.x)
            g.x = w;
    }

    g.parts.reverse();
}

function getPPixel(x, y)
{
    var p;
    if(y < 0 || y >= g.y)
    {
        p = '.';
    }
    else
    {
        var row = g.parts[y];
        if(x < 0 || x >= row.length)
        {
            p = '.';
        }
        else
        {
            p = row[x];
        }
    }
            
    return p;
}

function setPPixel(x,y, c)
{
    var gg = g;
    if(y < 0 || y >= gg.y)
        return;
    var yRow = gg.parts[y];
    if(x < 0 || x >= yRow.length)
        return;
    
    yRow = yRow.substr(0, x) + c + yRow.substr(x+1);
    g.parts[y] = yRow;
}

function point(x,y)
{
    var point = {};
    point.x = x;
    point.y = y;
    return point;
}

function buildUnitSegmentsList()
{
    // scan all the pixels, and build the Big Bag of Unit-length segments.
    // g.segments[] is an array of objects, each segment.p0[2], segment.p1[2]
    for(var y = 0; y <= g.y; y++)
    {
        for(x = 0; x < g.x; x++)
        {
            var kAbove = getPPixel(x,y-1);
            var kBelow = getPPixel(x,y);
            if(kAbove != kBelow)
            {
                // one segment.
                var segment = {};
                segment.p0 = point(x,y);
                segment.p1 = point(x+1,y);
                g.segments.push(segment);
            }
        }
    }

    for(var x = 0; x <= g.x; x++)
    {
        for(y = 0; y < g.y; y++)
        {
            var kAbove = getPPixel(x-1,y);
            var kBelow = getPPixel(x,y);
            if(kAbove != kBelow)
            {
                // one segment.
                var segment = {};
                segment.p0 = point(x,y);
                segment.p1 = point(x,y+1);
                g.segments.push(segment);
            }
        }
    }
}

function pullSegment(ix)
{
    var gg = g;
    var segment = gg.segments.splice(ix, 1)[0];
    return segment;
}

function peq(pA, pB)
{
    if(pA.x == pB.x && pA.y == pB.y)
        return true;
    else
        return false;
}

function sign(a)
{
    if(a < 0)
        return -1
    if(a > 0)
        return +1;
    
    return 0;
}

function tryToExtendSegment(p0, p1)
{
    // look for a segment (p1, p2) which goes in exact same direction as (p0,p1).
    // (might be the opposite direction of how we recorded it)
    
    var gg = g;
    
    var p2 = {};
    p2.x = p1.x + sign(p1.x - p0.x);
    p2.y = p1.y + sign(p1.y - p0.y);

    for(var ix = 0; ix < g.segments.length; ix++)
    {
        var segment = g.segments[ix];
        
        var matchF = peq(p1, segment.p0) && peq(p2, segment.p1);
        var matchB = peq(p1, segment.p1) && peq(p2, segment.p0);
        if(matchF || matchB)
        {
            pullSegment(ix);
            return p2;
        }
    }
    return null;
}

function tryToConnect(p0)
{
    var gg = g;
    var result = {};
    for(var ix = 0; ix < gg.segments.length; ix++)
    {
        var segment = gg.segments[ix];
        if(peq(p0, segment.p0))
        {
            pullSegment(ix);
            return segment.p1;
        }
        if(peq(p0, segment.p1))
        {
            pullSegment(ix);
            return segment.p0;
        }
    }
    return null;
}

function pullSegmentRun(firstSegment)
{
    // return an array of points which are a greedily-found set of connected lines
    // of the pattern.
    var result = [];
    result.push(firstSegment.p0);
    
    var previousPoint = firstSegment.p0;
    var currentPoint = firstSegment.p1;
    while(true)
    {
        // find a segment that extends the current one, if possible
        var nextPoint = tryToExtendSegment(previousPoint, currentPoint); // removes it from g.segments, if found.
        if(nextPoint)
        {
            previousPoint = currentPoint;
            currentPoint = nextPoint;
        }
        else
        {
            // no next segment along straight path. Wrap up this vector...
            result.push(currentPoint);
            
            // and see if there's another we can connect to (in any direction)
            var nextPoint = tryToConnect(currentPoint);
            if(!nextPoint)
                break;
            
            // we did!
            previousPoint = currentPoint;
            currentPoint = nextPoint;
        }
    }

    // also work backwards...
    while(true)
    {
        currentPoint = result[0];
        previousPoint = result[1];
        var nextPoint = tryToExtendSegment(previousPoint, currentPoint);
        if(nextPoint)
        {
            result[0] = nextPoint;
        }
        else
        {
            var nextPoint = tryToConnect(currentPoint);
            if(!nextPoint)
                break;
            result.splice(0, 0, nextPoint);
        }
    }

    return result;
}

function buildLongSegments()
{
    var result = [];
    // given an array of unit segments, return an array of multi-unit segments
    // destroys the input g.segments.

    var gg = g;
    while(gg.segments.length > 0)
    {
        var longSegment = pullSegment(0);
        while(true)
        {
            var nextPoint = tryToExtendSegment(longSegment.p0, longSegment.p1);
            if(nextPoint)
                longSegment.p1 = nextPoint;
            else
                break;
        }

        while(true)
        {
            var nextPoint = tryToExtendSegment(longSegment.p1, longSegment.p0);
            if(nextPoint)
                longSegment.p0 = nextPoint;
            else
                break;
        }
        
        result.push(longSegment);
    }
    return result;
}

function floodShape(x, y, newChar, oldChar)
{
    if(x < 0 || x >= g.x || y < 0 || y >= g.y)
        return;
    
    if(!oldChar)
        oldChar = getPPixel(x,y);
        
    var cNow = getPPixel(x,y);
    
    if(cNow != oldChar)
        return;

    setPPixel(x,y,newChar);
    floodShape(x-1,y, newChar, oldChar);
    floodShape(x+1,y, newChar, oldChar);
    floodShape(x,y-1, newChar, oldChar);
    floodShape(x,y+1, newChar, oldChar);
}

function addHolesPerShape(cx, cy, s, holeSize, justOneHolePerShape)
{
	var layer = newLayer(app.documents[0],"holes_");
    var gg = g;
    for(var y = gg.y - 1; y >= 0; y--)
    {
        for(var x = 0; x < gg.x; x++)
        {
            var c = getPPixel(x,y);
            if(c != '.')
            {
                var pathItem = layer.pathItems.add();
                pathItem.filled = false;
                pathItem.closed = true;
                var points = pathItem.pathPoints; // brand new, empty
                var hR = holeSize / 2;
                var hCx = cx + x * s + s/2;
                var hCy = cy + y * s + s/2;
                addP(points, hCx - hR, hCy - hR);
                addP(points, hCx - hR, hCy + hR);
                addP(points, hCx + hR, hCy + hR);
                addP(points, hCx + hR, hCy - hR);
                
                if(justOneHolePerShape)
                    floodShape(x, y, '.');
            }
        }
    }
}


function main()
{
    var docRef = documents[0];
	var docHeight = docRef.height;
	var docWidth = docRef.width;

    // default values
	var cx = 0;
	var cy = -docHeight;
    var gg = g;

	var layer = newLayer(app.documents[0],"blips_");
	
    
    scanPattern();
    buildUnitSegmentsList();

    var s = 72;

    g.segments = buildLongSegments();

    // walk segments until none remain
    while(g.segments.length > 0)
    {
        var segment = pullSegment(0);
        var segmentRun = pullSegmentRun(segment);
        g.segmentRuns.push(segmentRun);
    }

    // add segments...
    for(var ix = 0; ix < g.segmentRuns.length; ix++)
    {
        var pathItem = layer.pathItems.add();
        pathItem.filled = false;
        pathItem.closed = false;
        var points = pathItem.pathPoints; // brand new, empty
        var segmentRun = g.segmentRuns[ix];
        
        if(peq(segmentRun[0], segmentRun[segmentRun.length - 1]))
        {
            // first and last meet, make it a closed vector.
            segmentRun.splice(segmentRun.length - 1, 1);
            pathItem.closed = true;
        }
        for(var px = 0; px < segmentRun.length; px++)
        {
            var p = segmentRun[px];
            addP(points, cx + p.x * s, cy + p.y * s);
        }
    }

    addHolesPerShape(cx, cy, s, s * 0.25, true);
}

main();