package org.ros.chatto.captcha;

import javax.imageio.ImageIO;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Random;

/**
 * This class represents a simple captcha consisting
 * of an image {@code png} and its text value.
 * Comic Neue Bold Font.
 * Capital english letters {@code ONLY}.
 *
 * @since 1.3
 * @author Gennadiy Golovin
 */
public final class SimpleCaptcha {

    private BufferedImage imagePng;
    private char[] text;

    /**
     * Initializes a newly created default object
     * consisting of 8 capital english letters.
     */
    public SimpleCaptcha() {
        this.text = getRandomChars();

        try {
            generateCaptcha();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * Initializes a newly created object, which length
     * depends on the passed {@code int} parameter,
     * which {@code MUST} be greater than 0.
     * If the condition is not met, initializes a newly
     * created default object consisting of 8 symbols.
     *
     * @param length the quantity of symbols, that the
     *               captcha consists of, greater than 0.
     */
    public SimpleCaptcha(int length) {
        if (length < 1) {
            this.text = getRandomChars();
        } else {
            this.text = getRandomChars(length);
        }

        try {
            generateCaptcha();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * Initializes a newly created object based on the passed
     * {@link String} parameter, consisting of capital english
     * letters. If the condition is not met, initializes a newly
     * created default object consisting of 8 capital english letters.
     *
     * @param text the text string with the value of the captcha,
     *             length greater than 0.
     */
    public SimpleCaptcha(String text) {
        if (text == null || text.equals("")) {
            this.text = getRandomChars();
        } else {
            this.text = text.toCharArray();
        }

        try {
            generateCaptcha();
        } catch (IOException e) {
            this.text = getRandomChars();
            try {
                generateCaptcha();
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
    }

    /**
     * Returns the picture with captcha
     *
     * @return {@link BufferedImage}
     */
    public BufferedImage getImagePng() {
        return imagePng;
    }

    /**
     * Returns the text value of the captcha
     *
     * @return {@link String}
     */
    public String getText() {
        return String.valueOf(text);
    }

    //////// //////// //////// //////// //////// //////// //////// ////////

    private char[] getRandomChars() {
        return getRandomChars(8);
    }

    private char[] getRandomChars(int quantity) {

        char[] randomString = new char[quantity];

        Random random = new Random();

        int capitalLetter;

        for (int i = 0; i < quantity; i++) {
            capitalLetter = 65 + random.nextInt(26);
            randomString[i] = (char) capitalLetter;
        }

        return randomString;
    }

    private void generateCaptcha() throws IOException {
        int charsQuantity = this.text.length;
        BufferedImage[] images = new BufferedImage[charsQuantity];

        for (int i = 0; i < charsQuantity; i++) {
            images[i] = ImageIO.read(SimpleCaptcha.class.getResourceAsStream("/pictures/" + this.text[i] + ".png"));
            if (i % 2 == 0) {
                images[i] = rotateImage(images[i], 25);
            } else {
                images[i] = rotateImage(images[i], -20);
            }
        }

        int imageSize = 30;
        int rotatedImageSize = (int) Math.sqrt(imageSize * imageSize * 2);

        BufferedImage captchaImg = new BufferedImage(rotatedImageSize * (charsQuantity - 1) / 10 * 6 + rotatedImageSize, rotatedImageSize, BufferedImage.TYPE_INT_ARGB);
        Graphics2D graphics2d = captchaImg.createGraphics();
        graphics2d.setBackground(Color.WHITE);
        graphics2d.clearRect(0, 0, captchaImg.getWidth(), captchaImg.getHeight());
        for (int i = 0; i < charsQuantity; i++) {
            captchaImg.getGraphics().drawImage(images[i], rotatedImageSize * i / 10 * 6, 0, null);
        }
        graphics2d.dispose();
        this.imagePng = captchaImg;
    }

    private BufferedImage rotateImage(BufferedImage buffImage, double angle) {

        double radian = Math.toRadians(angle);
        double sin = Math.abs(Math.sin(radian));
        double cos = Math.abs(Math.cos(radian));

        int width = buffImage.getWidth();
        int height = buffImage.getHeight();

        int nWidth = (int) Math.floor((double) width * cos + (double) height * sin);
        int nHeight = (int) Math.floor((double) height * cos + (double) width * sin);

        BufferedImage rotatedImage = new BufferedImage(nWidth, nHeight, BufferedImage.TYPE_INT_ARGB);

        Graphics2D graphics = rotatedImage.createGraphics();

        graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
        graphics.translate((nWidth - width) / 2, (nHeight - height) / 2);
        graphics.rotate(radian, (double) (width / 2), (double) (height / 2));
        graphics.drawImage(buffImage, 0, 0,null);
        graphics.dispose();

        return rotatedImage;
    }
}