# Source: ui/base/abstract-image.js

    /*global require, exports*/

    /**
     * @module montage/ui/base/abstract-image.reel
     */
    var Component = require("../component").Component,
        Url = require("../../core/mini-url"),
        Map = require("collections/map");

        if (typeof window !== "undefined") { // client-side
            Map = window.Map || Map;
        }

    /**
     * @class AbstractImage
     * @extends Component
     */
    exports.AbstractImage = Component.specialize( /** @lends AbstractImage# */ {

        constructor: {
            value: function AbstractImage() {
                if(this.constructor === AbstractImage) {
                    throw new Error("AbstractImage cannot be instantiated.");
                }

                this.addPathChangeListener("_ownerDocumentPart", this, "_rebaseSrc");
            }
        },

        // http://stackoverflow.com/questions/6018611/smallest-data-uri-image-possible-for-a-transparent-image
        emptyImageSrc: {
            value: "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"
        },

        _isLoadingImage: {
            value: false
        },

        _image: {
            value: null
        },

        _src: {
            value: null
        },

        src: {
            set: function (value) {
                if (this._src !== value) {
                    this._src = value;

                    value = this._getRebasedSrc();
                    if (value) {
                        this._isInvalidSrc = false;
                        this._loadImage(value);
                    } else {
                        this._isInvalidSrc = true;
                    }
                    this.needsDraw = true;
                }
            },
            get: function () {
                return this._src;
            }
        },

        _loadImage: {
            value: function (src) {
                    if(!this._image || src !== this._image.src ) {
                        if(this._image) {
                            this.constructor.checkinImage(this._image);
                        }
                        this._image = this.constructor.checkoutImageWithUrl(src,this);
                        this._isLoadingImage = !this._image.complete;
                    }
            }
        },

        // Invalid source is set when the src property is a relative location that
        // the image was not able to rebase using the templates baseUrl or any
        // other means. It can also mean that there is no src.
        _isInvalidSrc: {
            value: true
        },

        width: {
            value: null
        },

        height: {
            value: null
        },

        _textAlternative: {
            value: null
        },

        textAlternative: {
            set: function (value) {
                this._textAlternative = value;
                this.needsDraw = true;
            },
            get: function () {
                return this._textAlternative;
            }
        },

        _crossOrigin: {
            value: null
        },

        crossOrigin: {
            set: function (value) {
                if (value !== this._crossOrigin) {
                    this._crossOrigin = value;
                    this.needsDraw = true;
                }
            },
            get: function () {
                return this._crossOrigin;
            }
        },

        _rebaseSrc: {
            value: function () {
                var value;

                value = this._getRebasedSrc();

                if (value) {
                    this._isInvalidSrc = false;
                    this._loadImage(value);
                    this.needsDraw = true;
                }
            }
        },

        // Returns a rebased version of the "src" property using the owner template
        // location when available. If a rebased version of the url is not possible
        // then null is returned.
        // If the "src" property is an absolute url ("http://" "https://" "/" "//"
        // "data:") then no operation is performed and the "src" ir returned as is.
        _getRebasedSrc: {
            value: function () {
                return this._getRebasedUrl(this._src);
            }
        },

        _getRebasedEmptySrc: {
            value: function () {
                return this._getRebasedUrl(this.emptyImageSrc);
            }
        },

        _getRebasedUrl: {
            value: function (url) {
                var baseUrl,
                    // Check for "<protocol>:", "/" and "//"
                    absoluteUrlRegExp = /^[\w\-]+:|^\//;

                if (url) {
                    if (absoluteUrlRegExp.test(url)) {
                        return url;
                    } else if (this._ownerDocumentPart) {
                        baseUrl = this._ownerDocumentPart.template.getBaseUrl();
                        if (baseUrl) {
                            return Url.resolve(baseUrl, url);
                        }
                    }
                }

                return null;
            }
        },

        enterDocument: {
            value: function (firstTime) {
                if (firstTime) {
                    this.element.setAttribute("role", "img");
                }
            }
        },

        draw: {
            value: function () {
                var src;

                if (this._isLoadingImage || this._isInvalidSrc) {
                    src = this._getRebasedEmptySrc();
                } else {
                    src = this._getRebasedSrc();
                }

                // data: procotol is considered local and fires a CORS exception
                // when loaded in a non-localhost configuration because it doesn't
                // have the necessary properties for a cross-request.
                // From the spec it seems there is a special case for data: Urls
                // but at least Chrome seems to behave differently.
                // http://www.whatwg.org/specs/web-apps/current-work/multipage/fetching-resources.html#cors-settings-attribute
                if (this._crossOrigin === null || this._crossOrigin === undefined || src.slice(0, 5) === "data:") {
                    this.element.removeAttribute("crossorigin");
                } else {
                    this.element.setAttribute("crossorigin", this._crossOrigin);
                }

                this.element.src = src;
                this.element.setAttribute("aria-label", this._textAlternative);
            }
        },

        handleLoad: {
            value: function handleLoad(event) {
                this._isLoadingImage = false;
                this.needsDraw = true;
                event.target.removeEventListener("load",this,false);
            }
        }
    },

    //Construtor methods
    {
        "checkoutImageWithUrl": {
            value: function(url,imageComponent) {
                var cachedImage = this._imageCache.get(url);

                if (!cachedImage) {
                    cachedImage = this._imagePool.pop() || new Image();
                    cachedImage.addEventListener("load",imageComponent,false);
                    cachedImage.src = url;
                    this._imageCache.set(url,cachedImage);
                    this._imageCacheSize++;
                }
                else if (!cachedImage.complete) {
                    cachedImage.addEventListener("load",imageComponent,false);
                }
                else {
                    imageComponent.handleLoad({target:cachedImage});
                }
                //In case
                this._imagesToClear.delete(cachedImage);
                this._imageReferenceCount.set(cachedImage,(this._imageReferenceCount.get(cachedImage)||0)+1);

                return cachedImage;
            }
        },
        "checkinImage": {
            value: function(image) {
                var currentCount = this._imageReferenceCount.get(image)-1;
                if (currentCount === 0) {
                    this._imagesToClear.set(image,Date.now());
                    if(!this._clearImageInterval) {
                        this._clearImageInterval = setInterval(this._clearImage, this.clearCacheInterval, this);
                    }
                }
                else {
                    this._imageReferenceCount.set(image,currentCount);
                }
            }
        },
        "_clearImage": {
            value: function(self) {
                self._imagesToClear.forEach(function(lastUsed, image) {
                    if((Date.now() - lastUsed) > this.maxTimeUnused) {
                        self._imagePool.push(image);
                        self._imageReferenceCount.delete(image);
                        self._imageCache.delete(image.src);
                        image.src = void 0;
                    }
                });
            }
        },
        _imagePool: {
            value: []
        },
        _imagesToClear: {
            value: new Map()
        },
        _imageReferenceCount: {
            value: new Map()
        },
        _imageCache: {
            value: new Map()
        },
        clearCacheInterval: {
            value: 60000
        },
        maxTimeUnused: {
            value: 60000
        },
        _imageCacheSize: {
            value: 0
        }
    });