package com.fp.common.base64;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;

import com.fp.common.logger.APPLogger;

/**
 * Clase que se encarga de 
 * @author Jorge Vaca.
 * @version 2.1
 */
public class Base64Input extends FilterInputStream {
	private static final char[] BASE_64 = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
			'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
			'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4',
			'5', '6', '7', '8', '9', '+', '/' };

	/**
	 * Decodifica un array de bytes codificado.
	 * 
	 * @param contenido Array de bytes
	 * @return array de bytes decodificado
	 */
	public static String decodificar(String contenido) {
		ByteArrayOutputStream o = new ByteArrayOutputStream();
		ByteArrayInputStream i = new ByteArrayInputStream(contenido.replaceAll("\n", "").getBytes());
		Base64Input in = new Base64Input(i);

		try {
			while (in.available() > 0) {
				o.write(in.read());
			}
		} catch (IOException e) {
			APPLogger.getLogger().error(e.getMessage(), e);
		}
		return o.toString();
	}

	private int i = 0;

	private byte[] outbuf = new byte[3];

	/**
	 * Crea un nuevo objeto Base64InputStream.
	 * 
	 * @param in Stream de entrada
	 */
	public Base64Input(InputStream in) {
		super(in);
	}

	/**
	 * Esta es una implementacion especial que devuelve 1 si hay aun por leer y
	 * 0 si ya se acabo.
	 * 
	 * @return 1 si hay aun bytes por leer o 0 si no.
	 * @throws IOException En caso de que no se haya abierto el inputstream
	 */
	@Override
	public int available() throws IOException {
		return (this.in.available() + this.i > 0) ? 1 : 0;
	}

	private int byteDesdeChar() throws IOException {
		char c = (char) super.read();

		while ((c == '\n') && (super.available() > 0)) {
			c = (char) super.read(); // se salta las lineas
		}

		while ((c == '=') && (super.available() > 0)) {
			c = (char) super.read(); // se salta los iguales del final
		}

		for (int a = 0; a < BASE_64.length; a++) {
			if (BASE_64[a] == c) {
				this.i++;

				return a;
			}
		}

		return 0;
	}

	/**
	 * Indica si se soporta marcas.
	 * 
	 * @return false siempre
	 */
	@Override
	public boolean markSupported() {
		return false;
	}

	/**
	 * Lee un caracter.
	 * 
	 * @return int con el caracter
	 * @throws IOException en caso de error
	 */
	@Override
	public int read() throws IOException {
		if (this.i == 0) {
			int a = this.byteDesdeChar();
			int b = this.byteDesdeChar();
			int c = this.byteDesdeChar();
			int d = this.byteDesdeChar();
			int j = this.i--;

			if (this.i > 0) {
				this.outbuf[2 - --this.i] = (byte) (a << 2 | b >> 4);
			}

			if (this.i > 0) {
				this.outbuf[2 - --this.i] = (byte) (b << 4 | c >> 2);
			}

			if (this.i > 0) {
				this.outbuf[2 - --this.i] = (byte) (c << 6 | d >> 0);
			}

			this.i += j - 1;
		}

		return this.outbuf[2 - --this.i];
	}

	/**
	 * Lee un array de bytes.
	 * 
	 * @param b Array
	 * @return entero con el resultado
	 * @throws IOException en caso de error
	 */
	@Override
	public int read(byte[] b) throws IOException {
		throw new IOException("Metodo no implementado");
	}

	/**
	 * Lee un array de caracteres con un offset y longitud.
	 * 
	 * @param b Array
	 * @param off Offset
	 * @param len Longitud
	 * @return entero con el resultado
	 * @throws IOException en caso de error
	 */
	@Override
	public int read(byte[] b, int off, int len) throws IOException {
		throw new IOException("Metodo no implementado");
	}

	/**
	 * Se salta n bytes del stream. No implementado.
	 * 
	 * @param n Numero de bytes
	 * @return long con el resultado
	 * @throws IOException en caso de error
	 */
	@Override
	public long skip(long n) throws IOException {
		throw new IOException("Metodo no implementado");
	}

}
