/*
 * image_edit.js
 * 
 * 画像編集用クラス
 * 　明るさ、コントラスト、色温度の変更およびクロップ機能を提供する
 * 
 */
class ImageEditor {
    constructor(file, temperatureInput, tintInput, brightnessInput, canvas, maxWidth, maxHeight) {
        this.temperatureInput = document.getElementById(temperatureInput);
        this.tintInput = document.getElementById(tintInput);
        this.brightnessInput = document.getElementById(brightnessInput);
        this.canvas = document.getElementById(canvas);
        this.ctx = this.canvas.getContext('2d', { willReadFrequently: true });
        this.image = new Image();
        this.originalImageData = null;
        this.adjustmentsImageData = null;
        this.startX = -1; this.startY = -1; this.endX = -1; this.endY = -1;
        this.temperatureInput.addEventListener('input', () => this.updateAdjustments());
        this.tintInput.addEventListener('input', () => this.updateAdjustments());
        this.brightnessInput.addEventListener('input', () => this.updateAdjustments());
        this.maxWidth = maxWidth;
        this.maxHeight = maxHeight;
        this.initCrop();

        this.readCanvas(file);
    }

    editImage(file, maxWidth, maxHeight) {
        this.temperatureInput.value = 0;
        this.tintInput.value = 0;
        this.brightnessInput.value = 0;
        this.maxWidth = maxWidth;
        this.maxHeight = maxHeight;
        this.startX = -1; this.startY = -1; this.endX = -1; this.endY = -1;
        this.readCanvas(file);
    }

    readCanvas(file) {
        if (file) {
            const reader = new FileReader();
            reader.onload = (e) => {
                this.image.src = e.target.result;
                this.image.onload = () => {
                    this.resizeCanvas();
                    this.ctx.drawImage(this.image, 0, 0, this.canvas.width, this.canvas.height);
                    this.originalImageData = this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height);
                    this.temperatureInput.value = 0;
                    this.tintInput.value = 0;
                    this.updateAdjustments();
                };
            };
            reader.readAsDataURL(file);
        }
    }

    resizeCanvas() {
        if (this.image.width > this.maxWidth || this.image.height > this.height) {
            let w = this.image.width;
            let h = this.image.height;
            let scale = 1;
            if (w > this.maxWidth) {
                scale = this.maxWidth / this.image.width;
                w = this.maxWidth;
                h = this.image.height * scale;
            }
            if (h > this.maxHeight) {
                scale = this.maxHeight / h;
                h = h * scale;
                w = w * scale;
            }
            this.canvas.width = w;
            this.canvas.height = h;

        } else {
            this.canvas.width = this.image.width;
            this.canvas.height = this.image.height;
        }
    }

    updateAdjustments() {
        let temperatureInputH = "";
        if (this.temperatureInput.value > 0) {
            temperatureInputH = "+";
        }

        let tintInputH = "";
        if (this.tintInput.value > 0) {
            tintInputH = "+";
        }

        let brightnessInputH = "";
        if (this.brightnessInput.value > 0) {
            brightnessInputH = "+";
        }
        document.getElementById('temperatureValue').textContent = temperatureInputH + this.temperatureInput.value;
        document.getElementById('tintValue').textContent = tintInputH + this.tintInput.value;
        document.getElementById('brightnessValue').textContent = brightnessInputH + this.brightnessInput.value;
        this.applyAdjustments();

        if (this.startX != -1) {
            //クロップ範囲を描画
            this.drawCropRect(this.startX, this.startY, this.endX, this.endY);
        }
    }


    clamp(value, min, max) {
        return Math.max(min, Math.min(max, value));
    }

    initCrop() {
        let isDragging = false;
        let isResizing = false;
        let isMove = false;

        let moveStartX = -1, moveStartY = -1;
        let currentHandle = null;

        const handleSize = 8;
        const handles = [
            { cursor: 'nw-resize', x: -handleSize / 2, y: -handleSize / 2 },
            { cursor: 'ne-resize', x: handleSize / 2, y: -handleSize / 2 },
            { cursor: 'sw-resize', x: -handleSize / 2, y: handleSize / 2 },
            { cursor: 'se-resize', x: handleSize / 2, y: handleSize / 2 }
        ];

        this.canvas.addEventListener('pointerdown', (e) => {
            e.preventDefault();
            e.target.setPointerCapture(e.pointerId)
            const [x, y] = [e.offsetX, e.offsetY];
            const handle = this.getResizeHandle(x, y, this.startX, this.startY, this.endX, this.endY, handleSize);
            if (handle) {
                isResizing = true;
                currentHandle = handle;
                return;
            }

            if (this.isInsideCropRect(x, y, this.startX, this.startY, this.endX, this.endY)) {
                //範囲内でマウスダウンした場合はクロップ範囲移動
                moveStartX = x;
                moveStartY = y;
                isMove = true;
                return;
            }
            else {
                //範囲外でマウスダウンされた場合はクロップ選択解除
                this.clearCropRect();
                this.startX = -1;
                this.startY = -1;
                this.endX = -1;
                this.endY = -1;
            }

            isDragging = true;
            this.startX = x;
            this.startY = y;
            this.endX = x;
            this.endY = y;

        });

        this.canvas.addEventListener('pointermove', (e) => {
            e.preventDefault();
            let [x, y] = [e.offsetX, e.offsetY];
            if (x < 0) { x = 0; }
            if (x > this.canvas.width) { x = canvas.width; }
            if (y < 0) { y = 0; }
            if (y > this.canvas.height) { y = canvas.height; }

            if (isDragging || isResizing) {

                if (isResizing && currentHandle) {
                    [this.startX, this.startY, this.endX, this.endY] = this.resizeCropRect(x, y, this.startX, this.startY, this.endX, this.endY, currentHandle);
                } else {
                    this.endX = x;
                    this.endY = y;
                }
                if (e.shiftKey) {
                    //シフトキー押下時は正方形に制御する
                    let width = this.endX - this.startX;
                    this.endY = this.startY + width;
                }


                this.drawCropRect(this.startX, this.startY, this.endX, this.endY);
            } else if (isMove) {
                let tmpStartX = this.startX + (x - moveStartX);
                let tmpStartY = this.startY + (y - moveStartY);
                let tmpWidth = (this.endX - this.startX);
                let tmpHeight = (this.endY - this.startY);

                if (tmpStartX < 0) {
                    x = x + (0 - tmpStartX);
                }
                if (tmpStartY < 0) {
                    y = y + (0 - tmpStartY);
                }

                this.startX += (x - moveStartX);
                this.startY += (y - moveStartY);
                this.endX += (x - moveStartX);
                this.endY += (y - moveStartY);

                if (this.endX > this.canvas.width) {
                    this.startX = this.canvas.width - tmpWidth;
                    this.endX = this.startX + tmpWidth;
                }
                if (this.endY > this.canvas.height) {
                    this.startY = this.canvas.height - tmpHeight;
                    this.endY = this.startY + tmpHeight;
                }


                this.drawCropRect(this.startX, this.startY, this.endX, this.endY);
                moveStartX = x;
                moveStartY = y;
            } else {
                if (this.startX == -1) {
                    return;
                }
                const handle = this.getResizeHandle(x, y, this.startX, this.startY, this.endX, this.endY, handleSize);
                this.canvas.style.cursor = handle ? handle.cursor : 'default';
            }
        });

        this.canvas.addEventListener('pointerup', (e) => {
            e.preventDefault();
            if (isDragging || isResizing) {
                isDragging = false;
                isResizing = false;
                currentHandle = null;
            }
            isMove = false;


            if (this.endX < this.startX) {
                [this.startX, this.endX] = [this.endX, this.startX];
            }
            if (this.endY < this.startY) {
                [this.startY, this.endY] = [this.endY, this.startY];
            }
            if (this.startX == this.endX || this.startY == this.endY) {
                this.clearCropRect();
                this.startX = -1;
                this.startY = -1;
                this.endX = -1;
                this.endY = -1;
            }


        });
    }

    drawCropRect(startX, startY, endX, endY) {

        this.ctx.putImageData(this.adjustmentsImageData, 0, 0);

        if (endX < startX) {
            [startX, endX] = [endX, startX];
        }
        if (endY < startY) {
            [startY, endY] = [endY, startY];
        }

        // 暗くする範囲を描画
        this.ctx.fillStyle = 'rgba(0, 0, 0, 0.5)';
        this.ctx.fillRect(0, 0, this.canvas.width, startY);
        this.ctx.fillRect(0, startY, startX, endY - startY);
        this.ctx.fillRect(endX, startY, this.canvas.width - endX, endY - startY);
        this.ctx.fillRect(0, endY, this.canvas.width, this.canvas.height - endY);

        this.ctx.strokeStyle = 'red';
        this.ctx.lineWidth = 2;
        this.ctx.strokeRect(startX, startY, endX - startX, endY - startY);

        this.drawHandles(startX, startY, endX, endY);
    }

    drawHandles(startX, startY, endX, endY) {
        const handleSize = 8;
        this.ctx.fillStyle = 'red';
        this.ctx.fillRect(startX - handleSize / 2, startY - handleSize / 2, handleSize, handleSize);
        this.ctx.fillRect(endX - handleSize / 2, startY - handleSize / 2, handleSize, handleSize);
        this.ctx.fillRect(startX - handleSize / 2, endY - handleSize / 2, handleSize, handleSize);
        this.ctx.fillRect(endX - handleSize / 2, endY - handleSize / 2, handleSize, handleSize);
    }

    getResizeHandle(x, y, startX, startY, endX, endY, handleSize) {
        const handles = [
            { cursor: 'nw-resize', x: startX, y: startY, name: 'nw' },
            { cursor: 'ne-resize', x: endX, y: startY, name: 'ne' },
            { cursor: 'sw-resize', x: startX, y: endY, name: 'sw' },
            { cursor: 'se-resize', x: endX, y: endY, name: 'se' }
        ];

        return handles.find(handle =>
            x >= handle.x - handleSize / 2 && x <= handle.x + handleSize / 2 &&
            y >= handle.y - handleSize / 2 && y <= handle.y + handleSize / 2
        );
    }

    resizeCropRect(x, y, startX, startY, endX, endY, handle) {
        switch (handle.name) {
            case 'nw':
                startX = x;
                startY = y;
                break;
            case 'ne':
                endX = x;
                startY = y;
                break;
            case 'sw':
                startX = x;
                endY = y;
                break;
            case 'se':
                endX = x;
                endY = y;
                break;
        }
        return [startX, startY, endX, endY];
    }

    isInsideCropRect(x, y, startX, startY, endX, endY) {
        return x >= startX && x <= endX && y >= startY && y <= endY;
    }

    clearCropRect() {
        this.ctx.putImageData(this.adjustmentsImageData, 0, 0);
    }

    getCropImage(startX, startY, endX, endY) {
        if (startX == -1) {
            //クロップなし
            startX = 0;
            startY = 0;
            endX = this.canvas.width;
            endY = this.canvas.height;
        }
        const cropWidth = endX - startX;
        const cropHeight = endY - startY;
        const scaleX = this.image.width / this.canvas.width;
        const scaleY = this.image.height / this.canvas.height;
        this.originalStartX = startX * scaleX;
        this.originalStartY = startY * scaleY;
        this.cropWidth = cropWidth * scaleX;
        this.cropHeight = cropHeight * scaleY;

        const tempCanvas = document.createElement('canvas');
        tempCanvas.width = this.cropWidth;
        tempCanvas.height = this.cropHeight;
        const tempCtx = tempCanvas.getContext('2d', { willReadFrequently: true });
        tempCtx.drawImage(this.image, this.originalStartX, this.originalStartY, this.cropWidth, this.cropHeight, 0, 0, this.cropWidth, this.cropHeight);
        return tempCtx.getImageData(0, 0, this.cropWidth, this.cropHeight);
    }

    isEdit() {
        const temp = parseInt(this.temperatureInput.value, 10);
        const tint = parseInt(this.tintInput.value, 10);
        const brightness = parseInt(this.brightnessInput.value, 10);
        //編集が行われていない場合はfalseを返す
        if (this.startX == -1 && temp == 0 && tint == 0 && brightness == 0) {
            return false;
        }
        return true;
    }

    //色調補正を実行する
    getColorAdjustmentsData(data) {

        const temp = parseInt(this.temperatureInput.value, 10);
        const tint = parseInt(this.tintInput.value, 10);
        const brightness = parseInt(this.brightnessInput.value, 10);

        for (let i = 0; i < data.length; i += 4) {
            data[i] = this.clamp(data[i] + temp - (tint / 2) + brightness, 0, 255);      // Red
            data[i + 1] = this.clamp(data[i + 1] + tint + brightness, 0, 255); // Green
            data[i + 2] = this.clamp(data[i + 2] - temp - (tint / 2) + brightness, 0, 255); // Blue
        }

    }

    //プレビュー画像を更新する
    applyAdjustments() {
        this.ctx.putImageData(this.originalImageData, 0, 0);
        const imageData = this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height);
        const data = imageData.data;

        this.getColorAdjustmentsData(data);

        this.ctx.putImageData(imageData, 0, 0);
        this.adjustmentsImageData = imageData;
    }

    //編集済みイメージを取得する
    getResultImage() {

        //クロップ済みイメージを取得
        const imageData = this.getCropImage(this.startX, this.startY, this.endX, this.endY);
        const data = imageData.data;

        //色調整
        this.getColorAdjustmentsData(data);

        const tempCanvas = document.createElement('canvas');
        tempCanvas.width = this.cropWidth;
        tempCanvas.height = this.cropHeight;
        const tempCtx = tempCanvas.getContext('2d', { willReadFrequently: true });
        tempCtx.putImageData(imageData, 0, 0);

        return tempCanvas.toDataURL('image/jpeg');
    }

}