namespace ImageResources {
    enum NativeImageDataConstructionType {
        SIZE,
        BLANK,
    }

    // TODO: friend classes/friend assemblies
    // Alternatively re-architect things so Graphics2D doesn't need to access this.
    public class NativeImageData {

        private static int idAlloc = 0;

        // nativeData[0] -> native bitmap resource
        // nativeData[1] -> bitmap width
        // nativeData[2] -> bitmap height
        // nativeData[3] -> pixel edit session

        public int width;
        public int height;
        private int revisionId = 0;
        private int resourceId = NativeImageData.idAlloc++;
        public string resourceFingerprint;
        private bool _locked = false;

        public constructor(int instruction, List<object> args) {
            ImageResource other = null;
            switch (instruction) {
                case NativeImageDataConstructionType.SIZE:
                    this.width = (int) args[0];
                    this.height = (int) args[1];
                    break;
                case NativeImageDataConstructionType.BLANK:
                    break;
            }

            if (instruction != NativeImageDataConstructionType.BLANK) {
                $lib_imageresources_nativeImageDataInit(this, this.width, this.height);
            }

            this.updateResourceFingerprint();
        }

        public void updateResourceFingerprint() {
            this.resourceFingerprint = this.resourceId + ',' + this.revisionId;
        }

        public void lock() {
            this._locked = true;
            this.revisionId++;
            this.updateResourceFingerprint();
        }

        public void unlock() {
            $lib_imageresources_flushImageChanges(this);
            this._locked = false;
        }

        public void blit(ImageResource image, int x, int y) {
            NativeImageData other = image._nativeResource;
            other.verifyUnlocked();
            this.verifyLocked();

            int targetLeft = x;
            int targetTop = y;
            int targetRight = x + other.width;
            int targetBottom = y + other.height;
            int sourceLeft = 0;
            int sourceTop = 0;
            int sourceRight = other.width;
            int sourceBottom = other.height;
            if (targetRight < 0 ||
                targetBottom < 0 ||
                targetTop >= this.height ||
                targetLeft >= this.width) return;

            if (targetRight >= this.width) {
                int diff = targetRight - this.width;
                targetRight -= diff;
                sourceRight -= diff;
            }
            if (targetBottom >= this.height) {
                int diff = targetBottom - this.height;
                targetBottom -= diff;
                sourceBottom -= diff;
            }
            if (targetLeft < 0) {
                int diff = -targetLeft;
                targetLeft = 0;
                sourceLeft += diff;
            }
            if (targetTop < 0) {
                int diff = -targetTop;
                targetTop = 0;
                sourceTop += diff;
            }
            int w = sourceRight - sourceLeft;
            int h = sourceBottom - sourceTop;
            if (w == 0 || h == 0) return;

            // initializes the pixel edit session in nativeData[3] if not present
            $lib_imageresources_blit(this, other, targetLeft, targetTop, sourceLeft, sourceTop, w, h);
        }

        public void verifyUnlocked() {
            if (this._locked) throw new Core.InvalidOperationException("Image resource is locked and cannot be used.");
        }

        public void verifyLocked() {
            if (!this._locked) throw new Core.InvalidOperationException("Image resource must be locked.");
        }
    }
}
