var canvasRenderer = (function(){
    return {
        canvases: [],
        getNewCanvasId: function(prefix){
            var curId = 1;
            var curIdStr = prefix + "_" +String(curId)
            this.canvases.forEach(function(curCanvasWrapper){
                var curCanvas = curCanvasWrapper.canvas;
                curId = Math.max(curId, parseInt(curCanvas.id.substring(curCanvas.id.lastIndexOf("_")+1, curCanvas.id.length)))
            })
            curId++;
            return prefix + "_" +String(curId)
        },
        createCanvas: function(width, height, type){
            var curCanvas = document.createElement("canvas");
            curCanvas.height = height;
            curCanvas.width = width;
            curCanvas.id = this.getNewCanvasId(type);
            var curCanvasWrapper = {
                canvas: curCanvas,
                isBusy: false
            }
            this.canvases.push(curCanvasWrapper);
            return curCanvasWrapper
        },
        getTileMergerWrapper: function(tileSize){
            var freeCanvas
            this.canvases.forEach(function(canvasWrapper){
                if((!canvasWrapper.isBusy) && canvasWrapper.canvas.id.indexOf("tileMerger"+String(tileSize)) > -1){
                    freeCanvas = canvasWrapper
                }
            })
            if(!freeCanvas){
                freeCanvas = this.createCanvas(tileSize, tileSize, "tileMerger"+String(tileSize))
            }else{
                freeCanvas.canvas.getContext("2d").clearRect(0,0,freeCanvas.canvas.width, freeCanvas.canvas.height)
            }
            return freeCanvas
        },
        mergeTilesPromise: function(quadcode, outPath, serverprofile){
            var getTileUrlFunction = getTileserverApi(serverprofile).getTileUri
            if(serverprofile.tileSizeIn && serverprofile.tileSizeIn == 256 && serverprofile.tileSize == 512){
                var urls = tileFinder.getHigherZoomleverQuads(quadcode).map(function(higherquads){
                    return getTileUrlFunction(higherquads)
                })
                return this.merge4x256TilesPromise(urls, outPath, serverprofile.fileExtension)
            }else if(serverprofile.tileSizeIn && serverprofile.tileSizeIn == 512 && serverprofile.tileSize == 1024){
                var urls = tileFinder.getHigherZoomleverQuads(quadcode).map(function(higherquads){
                    return getTileUrlFunction(higherquads)
                })
                return this.merge4x512TilesPromise(urls, outPath, serverprofile.fileExtension)
            }else if(serverprofile.tileSizeIn && serverprofile.tileSizeIn == 256 && serverprofile.tileSize == 1024){
                var urls = [];
                tileFinder.getHigherZoomleverQuads(quadcode).forEach(function(higherquad){
                    tileFinder.getHigherZoomleverQuads(higherquad).forEach(function(highhigherquad){
                        urls.push(getTileUrlFunction(highhigherquad))
                    })
                })
                return this.merge16x256TilesPromise(urls, outPath, serverprofile.fileExtension)
            }else{
                return new Promise(function(resolve,reject){
                    reject({message:"tilemerger doesn't support in- and output constellation: " + String(serverprofile.tileSizeIn) + "px to " + String(serverprofile.tileSize) + "px."})
                })
            }
        },
        merge4x256TilesPromise: function(urls, outPath, fileFormat){
            return this.mergeToNextZoomlevelPromise(urls, outPath, fileFormat, 256)
        },
        merge16x256TilesPromise: function(urls, outPath, fileFormat){
            var thisObj = this;
            var urlPackages = [];
            for(var t = 0; t < 4; t++){
                urlPackages.push(urls.splice(0,4))
            }
            return new Promise(function(resolve, reject){
                Promise.all(urlPackages.map(function(urlPackage){
                    return thisObj.merge4x256TilesPromise(urlPackage, undefined, "png")
                })).then(function(dataUrls){
                    thisObj.merge4x512TilesPromise(dataUrls, outPath, fileFormat).then(function(pathToTile){
                        resolve(pathToTile)
                    }).catch(function(err){
                        reject(err)
                    })
                }).catch(function(err){
                    reject(err.message)
                })
            })
        },
        merge4x512TilesPromise: function(urls, outPath, fileFormat){
            return this.mergeToNextZoomlevelPromise(urls, outPath, fileFormat, 512)
        },
        mergeToNextZoomlevelPromise: function(urls, outPath, fileFormat, tileSize){
            var imageLoader = this.getLoadedImagePromise;
            var mergeCanvasWrapper = this.getTileMergerWrapper(tileSize*2);
            mergeCanvasWrapper.isBusy = true;
            var mergeCanvas = mergeCanvasWrapper.canvas;
            var context = mergeCanvas.getContext("2d");
            var renderFormat = "image/jpeg"
            var renderOptions = 1;
            if(fileFormat && fileFormat.toLowerCase().indexOf("png")>-1){
                renderFormat = "image/png"
                renderOptions = undefined
                fileFormat = "png"
            }else{
                fileFormat = "jpg"
            }
            return new Promise(function(resolve, reject){
                Promise.all(urls.map(function(curUrl){
                    return imageLoader(curUrl)
                })).then(function(imgs){
                    context.drawImage(imgs[0], 0, 0);
                    context.drawImage(imgs[1], tileSize, 0);
                    context.drawImage(imgs[2], 0, tileSize);
                    context.drawImage(imgs[3], tileSize, tileSize);
                    var curDataUrl = mergeCanvas.toDataURL(renderFormat, renderOptions);
                    if(curDataUrl){
                        if(outPath){
                            var bufferStream = new stream.Readable()
                            bufferStream.push(dataUriToBuffer(curDataUrl))
                            bufferStream.push(null)
                            fs.mkdirsSync(path.dirname(outPath + "." + fileFormat))
                            bufferStream.pipe(fs.createWriteStream(outPath + "." + fileFormat)).on("finish", function(){
                                mergeCanvasWrapper.isBusy = false;
                                resolve(outPath + "." + fileFormat)
                            }).on("error", function(err){
                                mergeCanvasWrapper.isBusy = false;
                                reject(err)
                            })
                        }else{
                            mergeCanvasWrapper.isBusy = false;
                            resolve(curDataUrl)
                        }
                    }else{
                        mergeCanvasWrapper.isBusy = false;
                        reject({message:"canvas couldn't be rendered"})
                    }
                }).catch(function(err){
                    mergeCanvasWrapper.isBusy = false;
                    reject(err)
                })
            });
        },
        getLoadedImagePromise: function(src){
            return new Promise(function(resolve, reject){
                var curImg = new Image()
                curImg.onload = function(){
                    resolve(curImg)
                }
                curImg.onerror = function(err){
                    reject(err)
                }
                curImg.src = src;
            })
        }
    }
})()