// Copyright (C) 2010, 2011, 2012 GlavSoft LLC.
// All rights reserved.
//
//-------------------------------------------------------------------------
// This file is part of the TightVNC software.  Please visit our Web site:
//
//                       http://www.tightvnc.com/
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//-------------------------------------------------------------------------
//

package com.glavsoft.drawing;

import com.glavsoft.exceptions.TransportException;
import com.glavsoft.rfb.encoding.PixelFormat;
import com.glavsoft.rfb.encoding.decoder.FramebufferUpdateRectangle;
import com.glavsoft.transport.Reader;

import java.util.Arrays;

/**
 * Render bitmap data
 *
 * @author dime @ tightvnc.com
 */
public abstract class Renderer {

	protected Reader reader;

	public abstract void drawJpegImage(byte[] bytes, int offset,
			int jpegBufferLength, FramebufferUpdateRectangle rect);

	protected int width;
	protected int height;
	protected int bytesPerPixel;
	protected int bytesPerPixelSignificant;
	protected int[] pixels;
	protected SoftCursor cursor;
	protected PixelFormat pixelFormat;
	private ColorDecoder colorDecoder;

	protected void init(Reader reader, int width, int height, PixelFormat pixelFormat) {
		this.reader = reader;
		this.width = width;
		this.height = height;
		initPixelFormat(pixelFormat);
		pixels = new int[width * height];
		Arrays.fill(pixels, 0);
	}

	public synchronized void initPixelFormat(PixelFormat pixelFormat) {
		this.pixelFormat = pixelFormat;
		bytesPerPixel = pixelFormat.bitsPerPixel / 8;
		bytesPerPixelSignificant =
				24 == pixelFormat.depth && 32 == pixelFormat.bitsPerPixel ? 3 : bytesPerPixel;
		colorDecoder = new ColorDecoder(pixelFormat);
	}

	/**
	 * Draw byte array bitmap data
	 *
	 * @param bytes bitmap data
	 * @param x bitmap x position
	 * @param y bitmap y position
	 * @param width bitmap width
	 * @param height bitmap height
	 */
	public void drawBytes(byte[] bytes, int x, int y, int width, int height) {
		int i = 0;
		for (int ly = y; ly < y + height; ++ly) {
			int end = ly * this.width + x + width;
			for (int pixelsOffset = ly * this.width + x; pixelsOffset < end; ++pixelsOffset) {
				pixels[pixelsOffset] = getPixelColor(bytes, i);
				i += bytesPerPixel;
			}
		}
	}

	/**
	 * Draw byte array bitmap data (for ZRLE)
	 */
	public synchronized int  drawCompactBytes(byte[] bytes, int offset, int x, int y, int width, int height) {
		int i = offset;
		for (int ly = y; ly < y + height; ++ly) {
			int end = ly * this.width + x + width;
			for (int pixelsOffset = ly * this.width + x; pixelsOffset < end; ++pixelsOffset) {
				pixels[pixelsOffset] = getCompactPixelColor(bytes, i);
				i += bytesPerPixelSignificant;
			}
		}
		return i - offset;
	}

	/**
	 * Draw int (colors) array bitmap data (for ZRLE)
	 */
	public synchronized void  drawColoredBitmap(int[] colors, int x, int y, int width, int height) {
		int i = 0;
		for (int ly = y; ly < y + height; ++ly) {
			int end = ly * this.width + x + width;
			for (int pixelsOffset = ly * this.width + x; pixelsOffset < end; ++pixelsOffset) {
				pixels[pixelsOffset] = colors[i++];
			}
		}
	}

	/**
	 * Draw byte array bitmap data (for Tight)
	 */
	public synchronized int drawTightBytes(byte[] bytes, int offset, int x, int y, int width, int height) {
		int i = offset;
		for (int ly = y; ly < y + height; ++ly) {
			int end = ly * this.width + x + width;
			for (int pixelsOffset = ly * this.width + x; pixelsOffset < end; ++pixelsOffset) {
				pixels[pixelsOffset] = colorDecoder.getTightColor(bytes, i);
				i += bytesPerPixelSignificant;
			}
		}
		return i - offset;
	}

	/**
	 * Draw byte array bitmap data (from array with plain RGB color components. Assumed: rrrrrrrr gggggggg bbbbbbbb)
	 */
	public synchronized void drawUncaliberedRGBLine(byte[] bytes, int x, int y, int width) {
		int end = y * this.width + x + width;
		for (int i=3, pixelsOffset = y * this.width + x; pixelsOffset < end; ++pixelsOffset) {
			pixels[pixelsOffset] =
//					(0xff & bytes[i++]) << 16 |
//					(0xff & bytes[i++]) << 8 |
//					0xff & bytes[i++];
					(0xff & 255 * (colorDecoder.redMax & bytes[i++]) / colorDecoder.redMax) << 16 |
					(0xff & 255 * (colorDecoder.greenMax & bytes[i++]) / colorDecoder.greenMax) << 8 |
					0xff & 255 * (colorDecoder.blueMax & bytes[i++]) / colorDecoder.blueMax;
		}
	}

	/**
	 * Draw paletted byte array bitmap data
	 *
	 * @param buffer bitmap data
	 * @param rect bitmap location and dimensions
	 * @param palette colour palette
	 */
	public synchronized void drawBytesWithPalette(byte[] buffer, FramebufferUpdateRectangle rect,
			int[] palette) {
				// 2 colors
				if (palette.length == 2) {
					int dx, dy, n;
					int i = rect.y * this.width + rect.x;
					int rowBytes = (rect.width + 7) / 8;
					byte b;

					for (dy = 0; dy < rect.height; dy++) {
						for (dx = 0; dx < rect.width / 8; dx++) {
							b = buffer[dy * rowBytes + dx];
							for (n = 7; n >= 0; n--) {
								pixels[i++] = palette[b >> n & 1];
							}
						}
						for (n = 7; n >= 8 - rect.width % 8; n--) {
							pixels[i++] = palette[buffer[dy * rowBytes + dx] >> n & 1];
						}
						i += this.width- rect.width;
					}
				} else {
					// 3..255 colors (assuming bytesPixel == 4).
					int i = 0;
					for (int ly =  rect.y; ly < rect.y + rect.height; ++ly) {
						for (int lx = rect.x; lx < rect.x + rect.width; ++lx) {
							int pixelsOffset = ly * this.width + lx;
							pixels[pixelsOffset] = palette[buffer[i++] & 0xFF];
						}
					}
				}

			}

	/**
	 * Copy rectangle region from one position to another. Regions may be overlapped.
	 *
	 * @param srcX source rectangle x position
	 * @param srcY source rectangle y position
	 * @param dstRect destination rectangle
	 */
	public synchronized void copyRect(int srcX, int srcY, FramebufferUpdateRectangle dstRect) {
		int startSrcY, endSrcY, dstY, deltaY;
		if (srcY > dstRect.y) {
			startSrcY = srcY;
			endSrcY = srcY + dstRect.height;
			dstY = dstRect.y;
			deltaY = +1;
		} else {
			startSrcY = srcY + dstRect.height - 1;
			endSrcY = srcY -1;
			dstY = dstRect.y + dstRect.height - 1;
			deltaY = -1;
		}
		for (int y = startSrcY; y != endSrcY; y += deltaY) {
			System.arraycopy(pixels, y * width + srcX,
					pixels, dstY * width + dstRect.x, dstRect.width);
			dstY += deltaY;
		}
	}

	/**
	 * Fill rectangle region with specified colour
	 *
	 * @param color colour to fill with
	 * @param rect rectangle region posions and dimensions
	 */
	public void fillRect(int color, FramebufferUpdateRectangle rect) {
		fillRect(color, rect.x, rect.y, rect.width, rect.height);
	}

	/**
	 * Fill rectangle region with specified colour
	 *
	 * @param color colour to fill with
	 * @param x rectangle x position
	 * @param y rectangle y position
	 * @param width rectangle width
	 * @param height rectangle height
	 */
	public synchronized void fillRect(int color, int x, int y, int width, int height) {
		int sy = y * this.width + x;
		int ey = sy + height * this.width;
		for (int i = sy; i < ey; i += this.width) {
			Arrays.fill(pixels, i, i + width, color);
		}
	}

	/**
	 * Reads color bytes (PIXEL) from reader, returns int combined RGB
	 * value consisting of the red component in bits 16-23, the green component
	 * in bits 8-15, and the blue component in bits 0-7. May be used directly for
	 * creation awt.Color object
	 */
	public int readPixelColor(Reader reader) throws TransportException {
		return colorDecoder.readColor(reader);
	}

	public int readTightPixelColor(Reader reader) throws TransportException {
		return colorDecoder.readTightColor(reader);
	}

	public ColorDecoder getColorDecoder() {
		return colorDecoder;
	}

	public int getCompactPixelColor(byte[] bytes, int offset) {
		return colorDecoder.getCompactColor(bytes, offset);
	}

	public int getPixelColor(byte[] bytes, int offset) {
		return colorDecoder.getColor(bytes, offset);
	}

	public int getBytesPerPixel() {
		return bytesPerPixel;
	}

	public int getBytesPerPixelSignificant() {
		return bytesPerPixelSignificant;
	}

	public void fillColorBitmapWithColor(int[] bitmapData, int decodedOffset, int rlength, int color) {
		while (rlength-- > 0) {
			bitmapData[decodedOffset++] = color;
		}
	}

	/**
	 * Width of rendered image
	 *
	 * @return width
	 */
	public int getWidth() {
		return width;
	}

	/**
	 * Height of rendered image
	 *
	 * @return height
	 */
	public int getHeight() {
		return height;
	}

	/**
	 * Read and decode cursor image
	 *
	 * @param rect new cursor hot point position and cursor dimensions
	 * @throws TransportException
	 */
	public void createCursor(int[] cursorPixels, FramebufferUpdateRectangle rect)
		throws TransportException {
		synchronized (cursor) {
			cursor.createCursor(cursorPixels, rect.x, rect.y, rect.width, rect.height);
		}
	}

	/**
	 * Read and decode new cursor position
	 *
	 * @param rect cursor position
	 */
	public void decodeCursorPosition(FramebufferUpdateRectangle rect) {
		synchronized (cursor) {
			cursor.updatePosition(rect.x, rect.y);
		}
	}

}