import _ from 'lodash';
import '../../../globals.js';
import { alpenglow, PolygonalFace, RenderAlpha, RenderBarycentricBlend, RenderBarycentricPerspectiveBlend, RenderBlendCompose, RenderColor, RenderColorSpaceConversion, RenderDepthSort, RenderFilter, RenderImage, RenderLinearBlend, RenderLinearGradient, RenderNormalDebug, RenderNormalize, RenderPathBoolean, RenderPhong, RenderProgramNeeds, RenderRadialBlend, RenderRadialGradient, RenderStack } from '../imports.js';
class RenderProgram {
    constructor(children, isFullyTransparent, isFullyOpaque, needsFace = false, needsArea = false, needsCentroid = false) {
        // Whether it is fully simplified (so simplification steps can be skipped)
        this.isSimplified = false;
        this.children = children;
        this.isFullyTransparent = isFullyTransparent;
        this.isFullyOpaque = isFullyOpaque;
        let hasPathBoolean = this instanceof RenderPathBoolean;
        for (let i = 0; i < children.length; i++) {
            const child = children[i];
            needsFace = needsFace || child.needsFace;
            needsArea = needsArea || child.needsArea;
            needsCentroid = needsCentroid || child.needsCentroid;
            hasPathBoolean = hasPathBoolean || child.hasPathBoolean;
        }
        this.needsFace = needsFace;
        this.needsArea = needsArea;
        this.needsCentroid = needsCentroid;
        this.hasPathBoolean = hasPathBoolean;
    }
    simplified() {
        if (this.isSimplified) {
            return this;
        }
        if (!RenderProgram.simplify) {
            this.isSimplified = true;
            return this;
        }
        let hasSimplifiedChild = false;
        const children = [];
        for (let i = 0; i < this.children.length; i++) {
            const child = this.children[i];
            const simplifiedChild = child.simplified();
            children.push(simplifiedChild);
            hasSimplifiedChild = hasSimplifiedChild || simplifiedChild !== child;
        }
        const potentialSimplified = this.getSimplified(children);
        if (potentialSimplified) {
            potentialSimplified.isSimplified = true; // convenience flag, so subtypes don't have to set it
            return potentialSimplified;
        }
        else if (hasSimplifiedChild) {
            const result = this.withChildren(children);
            result.isSimplified = true;
            return result;
        }
        else {
            this.isSimplified = true;
            return this;
        }
    }
    /**
     * This is an internal method for RenderPrograms to implement their simplification. It will get called by
     * simplified() with pre-simplified children.
     *
     * null should be returned when there is no simplification possible. The simplification engine will be able to use
     * this information to reduce the number of RenderPrograms created during simplification. (For instance, if
     * none of the children were actually simplified, it will be able to mark THIS RenderProgram as being
     * "fully simplified", and further calls to simplified() can short-circuit.
     */
    getSimplified(children) {
        return null;
    }
    writeInstructions(instructions) {
        throw new Error('unimplemented'); // TODO: make abstract
    }
    equals(other) {
        return this === other || (this.getName() === other.getName() &&
            this.children.length === other.children.length &&
            _.every(this.children, (child, i) => child.equals(other.children[i])) &&
            this.equalsTyped(other) // If they have the same name, should be the same type(!)
        );
    }
    equalsTyped(other) {
        return true;
    }
    getEvaluator() {
        return (context, output) => {
            return output.set(this.evaluate(context));
        };
    }
    /**
     * Returns a new RenderProgram with the given transform applied to it.
     *
     * NOTE: Default implementation, should be overridden by subclasses that have positioning information embedded inside
     */
    transformed(transform) {
        return this.withChildren(this.children.map(child => child.transformed(transform)));
    }
    // TODO: add early exit!
    depthFirst(callback) {
        this.children.forEach(child => child.depthFirst(callback));
        callback(this);
    }
    containsRenderProgram(renderProgram) {
        let result = false;
        this.depthFirst(candidateRenderProgram => {
            if (candidateRenderProgram === renderProgram) {
                result = true;
            }
            // TODO: early exit!!!!
        });
        return result;
    }
    replace(callback) {
        // TODO: preserve DAG!
        const replaced = callback(this);
        if (replaced) {
            return replaced;
        }
        else {
            return this.withChildren(this.children.map(child => child.replace(callback)));
        }
    }
    withPathInclusion(pathTest) {
        if (this instanceof RenderPathBoolean) {
            if (pathTest(this.path)) {
                return this.inside.withPathInclusion(pathTest);
            }
            else {
                return this.outside.withPathInclusion(pathTest);
            }
        }
        else {
            return this.withChildren(this.children.map(child => child.withPathInclusion(pathTest)));
        }
    }
    isSplittable() {
        return false;
    }
    split(face) {
        // TODO: should we handle the renderprogram splitting here?
        throw new Error('unimplemented');
    }
    getNeeds() {
        return new RenderProgramNeeds(this.needsFace, this.needsArea, this.needsCentroid);
    }
    colorConverted(fromSpace, toSpace) {
        return RenderColorSpaceConversion.convert(this, fromSpace, toSpace);
    }
    toRecursiveString(indent = '') {
        const extra = this.getExtraDebugString();
        let string = `${indent}${this.getName()}${extra ? ` (${extra})` : ''}`;
        this.children.forEach(child => {
            string += '\n' + child.toRecursiveString(indent + '  ');
        });
        return string;
    }
    getExtraDebugString() {
        return '';
    }
    print() {
        console.log(this.toRecursiveString());
    }
    static deserialize(obj) {
        if (obj.type === 'RenderStack') {
            return RenderStack.deserialize(obj);
        }
        else if (obj.type === 'RenderColor') {
            return RenderColor.deserialize(obj);
        }
        else if (obj.type === 'RenderAlpha') {
            return RenderAlpha.deserialize(obj);
        }
        else if (obj.type === 'RenderDepthSort') {
            return RenderDepthSort.deserialize(obj);
        }
        else if (obj.type === 'RenderBlendCompose') {
            return RenderBlendCompose.deserialize(obj);
        }
        else if (obj.type === 'RenderPathBoolean') {
            return RenderPathBoolean.deserialize(obj);
        }
        else if (obj.type === 'RenderFilter') {
            return RenderFilter.deserialize(obj);
        }
        else if (obj.type === 'RenderImage') {
            return RenderImage.deserialize(obj);
        }
        else if (obj.type === 'RenderNormalize') {
            return RenderNormalize.deserialize(obj);
        }
        else if (obj.type === 'RenderLinearBlend') {
            return RenderLinearBlend.deserialize(obj);
        }
        else if (obj.type === 'RenderBarycentricBlend') {
            return RenderBarycentricBlend.deserialize(obj);
        }
        else if (obj.type === 'RenderBarycentricPerspectiveBlend') {
            return RenderBarycentricPerspectiveBlend.deserialize(obj);
        }
        else if (obj.type === 'RenderLinearGradient') {
            return RenderLinearGradient.deserialize(obj);
        }
        else if (obj.type === 'RenderRadialBlend') {
            return RenderRadialBlend.deserialize(obj);
        }
        else if (obj.type === 'RenderRadialGradient') {
            return RenderRadialGradient.deserialize(obj);
        }
        else if (obj.type === 'RenderColorSpaceConversion') {
            return RenderColorSpaceConversion.deserialize(obj);
        }
        else if (obj.type === 'RenderPhong') {
            return RenderPhong.deserialize(obj);
        }
        else if (obj.type === 'RenderNormalDebug') {
            return RenderNormalDebug.deserialize(obj);
        }
        throw new Error(`Unrecognized RenderProgram type: ${obj.type}`);
    }
    // TODO: Prefer RenderEvaluationContext.getFace()
    // @deprecated
    static ensureFace(face, minX, minY, maxX, maxY) {
        return face || PolygonalFace.fromBoundsValues(minX, minY, maxX, maxY);
    }
    static closureIsFullyTransparent(renderProgram) {
        return renderProgram.isFullyTransparent;
    }
    static closureIsFullyOpaque(renderProgram) {
        return renderProgram.isFullyOpaque;
    }
    static closureSimplified(renderProgram) {
        return renderProgram.simplified();
    }
}
// Global flag for controlling whether simplification happens. Useful for debugging.
RenderProgram.simplify = true;
export default RenderProgram;
alpenglow.register('RenderProgram', RenderProgram);
//# sourceMappingURL=RenderProgram.js.map