package org.academiadecodigo.bootcamp11.calculator;

/**
 * MIT License
 * (c) 2017 Ricardo Constantino
 */
public class Calculator {

    private static int calculatorsManufactured;

    private String brand;
    private String color;
    private int manufactureId;

    private int lastResult;
    private float lastResultFloat;

    public Calculator() {
        setBrand("Texas Instruments");
        setColor("White");

        this.lastResult = 0;
        this.lastResultFloat = 0.0f;

        this.manufactureId = ++calculatorsManufactured;
    }

    public Calculator(String brand, String color) {
        setBrand(brand);
        setColor(color);

        this.lastResult = 0;
        this.lastResultFloat = 0.0f;

        this.manufactureId = calculatorsManufactured++;
    }

    public static int getCalculatorsManufactured() {
        return calculatorsManufactured;
    }

    private int getManufactureId() {
        return this.manufactureId;
    }

    private String getBrand() {
        return this.brand;
    }

    private void setBrand(String brand) {
        this.brand = brand;
    }

    private String getColor() {
        return this.color;
    }

    private void setColor(String color) {
        this.color = color;
    }

    public void showInfo() {
        System.out.println("This calculator is a " + getColor() +
                " " + getBrand() + " and has the manufacture ID " +
                getManufactureId() + ".");
    }

    public void calculate(Operation operator, float number1, float number2) {
        switch (operator) {
            case ADD:
                add(number1, number2);
                break;
            case SUBTRACT:
                sub(number1, number2);
                break;
            case MULTIPLY:
                mul(number1, number2);
                break;
            case DIVIDE:
                div(number1, number2);
                break;
            case SQUAREROOT:
                sqrt(number2);
                break;
            case EXPONENTIATION:
            case FACTORIAL:
                calculate(operator, (int) number1, (int) number2);
                return;
            default:
                System.err.println("There's no such operation.");
        }
        System.out.println(number1 + " " + operator.getSymbol() + " " + number2);
    }

    public void calculate(Operation operator, int number1, int number2) {
        switch (operator) {
            case EXPONENTIATION:
                pow(number1, number2);
                System.out.println(number1 + operator.getSymbol() + number2);
                return;
            case FACTORIAL:
                fact(number2);
                break;
            case ADD:
                add(number1, number2);
                break;
            case SUBTRACT:
                sub(number1, number2);
                break;
            case MULTIPLY:
                mul(number1, number2);
                break;
            case DIVIDE:
                div(number1, number2);
                break;
            case SQUAREROOT:
                calculate(operator, (float) number1, (float) number2);
                return;
            default:
                System.err.println("There's no such operation.");
        }
        System.out.println(number1 + " " + operator.getSymbol() + " " + number2);
    }

    public void calculate(Operation operator, float number) {
        switch(operator) {
            case SQUAREROOT:
                sqrt(number);
                break;
            case EXPONENTIATION:
            case FACTORIAL:
                calculate(operator, (int) number);
                return;
            case ADD:
            case SUBTRACT:
            case MULTIPLY:
            case DIVIDE:
                calculate(operator, this.lastResultFloat, number);
                return;
            default:
                System.err.println("There's no such operation.");
        }
        System.out.println(operator.getSymbol() + number);
    }

    public void calculate(Operation operator, int number) {
        switch (operator) {
            case FACTORIAL:
                fact(number);
                break;
            case EXPONENTIATION:
                calculate(operator, number, 2);
                return;
            case SQUAREROOT:
                sqrt(number);
                break;
            case ADD:
            case SUBTRACT:
            case MULTIPLY:
            case DIVIDE:
                calculate(operator, this.lastResultFloat, number);
                return;
            default:
                System.err.println("There's no such operation.");
        }
        System.out.println(operator.getSymbol() + number);
    }

    public void calculate(Operation operator) {
        switch (operator) {
            case FACTORIAL:
                calculate(operator, this.lastResult);
                return;
            case EQUAL:
                showResult();
                break;
            case RESET:
                resetResult();
                break;
            default:
                System.err.println("There's no such operation.");
        }
    }

    // Addition
    private void add(int numberOne, int numberTwo) {
        int result = numberOne + numberTwo;
        setResult(result);
    }

    private void add(float numberOne, float numberTwo) {
        float result = numberOne + numberTwo;
        setResult(result);
    }

    // Subtraction
    private void sub(int numberOne, int numberTwo) {
        int result = numberOne - numberTwo;
        setResult(result);
    }

    private void sub(float numberOne, float numberTwo) {
        float result = numberOne - numberTwo;
        setResult(result);
    }

    // Division
    private void div(float numberOne, float numberTwo) {
        float result = numberOne / numberTwo;
        setResult(result);
    }

    private void div(int numberOne, int numberTwo) {
        float result = numberOne / numberTwo;
        setResult(result);
    }

    // Multiplication
    private void mul(float numberOne, float numberTwo) {
        float result = numberOne * numberTwo;
        setResult(result);
    }

    private void mul(int numberOne, int numberTwo) {
        mul((float) numberOne, (float) numberTwo);
    }

    // Exponentation
    private void pow(float num, float power) {
        if (power == 0) {
            setResult(1);
            return;
        }
        float result = num;
        while (power-- > 1) {
            result *= num;
        }
        setResult(result);
    }

    // Factorial
    private void fact(float num) {
        if (num <= 1) {
            setResult(1);
            return;
        }
        float result = num;
        while (num-- > 1) {
            result *= num;
        }
        setResult(result);
    }

    private void fact(int num) {
        fact((float) num);
    }

    // Square root
    private void sqrt(float num) {
        float result = round(Math.sqrt(num), 6);
        setResult(result);
    }

    private void sqrt(int num) {
        sqrt((float) num);
    }

    private void resetResult() {
        lastResult = 0;
        lastResultFloat = 0.0f;
        showResult();
    }

    private void setResult(int result) {
        lastResult = result;
        lastResultFloat = Float.parseFloat(""+lastResult);
    }

    private void setResult(float result) {
        lastResult = (int) Math.round(Math.floor(result));
        lastResultFloat = result;
    }

    private void showResult() {
        if (lastResultFloat == Float.parseFloat(""+lastResult)) {
            System.out.println("= " + lastResult + "\n");
        } else {
            System.out.println("= " + round(lastResultFloat, 3) + "\n");
        }
    }

    private float round(float num, int decimal) {
        double mult = Math.pow(10, decimal);
        return (float) (Math.round((double) num * mult) / mult);
    }

    private float round(double num, int decimal) {
        double mult = Math.pow(10, decimal);
        return (float) (Math.round(num * mult) / mult);
    }

}
