/*
 * Copyright (c) 2009-2020, Peter Abeles. All Rights Reserved.
 *
 * This file is part of Efficient Java Matrix Library (EJML).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.ejml.dense.row.misc;

import org.ejml.CodeGeneratorMisc;

import java.io.FileNotFoundException;
import java.io.PrintStream;

/**
 * Generates code for an unrolled determinant by minor.
 *
 * NOTE:  There are some repeat calculations of inner determinants. Maybe it could be speed up by calculating those?
 *
 * @author Peter Abeles
 */
public class GenerateDeterminantFromMinor {

    protected PrintStream stream;
    protected int N;

    public GenerateDeterminantFromMinor( String fileName ) throws FileNotFoundException {
        stream = new PrintStream(fileName);
    }

    public GenerateDeterminantFromMinor( PrintStream stream ) {
        this.stream = stream;
    }

    public void createClass( int N ) {
        printTop(N);

        printCalls(N);

        print2();
        print3();
        for (int i = 4; i <= N; i++) {
            printFunction(i);
        }

        stream.print("}\n");
    }

    private void printTop( int N ) {
        String foo = CodeGeneratorMisc.COPYRIGHT +
                "\n" +
                "package org.ejml.dense.row.misc;\n" +
                "\n" +
                "import org.ejml.data.RowDMatrixD1;\n" +
                "\n" +
                "\n" +
                "/**\n" +
                " * This code was auto generated by  {@link GenerateDeterminantFromMinor} and should not be modified\n" +
                " * directly. \n" +
                " * \n" +
                " * @author Peter Abeles\n" +
                " */\n" +
                "public class UnrolledDeterminantFromMinor {\n" +
                "    \n" +
                "    public static final int MAX = " + N + ";\n";

        stream.print(foo);
    }

    private void print2() {
        stream.print("    public static double det2( RowDMatrixD1 mat )\n" +
                "    {\n" +
                "        return mat.get(0)*mat.get(3) - mat.get(1)*mat.get(2);\n" +
                "    }\n\n");
    }

    private void print3() {
        stream.print("    public static double det3( RowDMatrixD1 mat )\n" +
                "    {\n" +
                "        double a11 = mat.get( 0 );\n" +
                "        double a12 = mat.get( 1 );\n" +
                "        double a13 = mat.get( 2 );\n" +
                "        double a21 = mat.get( 3 );\n" +
                "        double a22 = mat.get( 4 );\n" +
                "        double a23 = mat.get( 5 );\n" +
                "        double a31 = mat.get( 6 );\n" +
                "        double a32 = mat.get( 7 );\n" +
                "        double a33 = mat.get( 8 );\n" +
                "\n" +
                "        double a = a11*(a22*a33 - a23*a32);\n" +
                "        double b = a12*(a21*a33 - a23*a31);\n" +
                "        double c = a13*(a21*a32 - a31*a22);\n" +
                "\n" +
                "        return a-b+c;\n" +
                "    }\n" +
                "\n");
    }

    private void printCalls( int N ) {
        stream.print(
                "    \n" +
                        "    public static double det( RowDMatrixD1 mat ) {\n");
        stream.print(
                "        if( mat.numRows == 2 ) {\n" +
                        "            return det2(mat);\n");
        for (int i = 3; i <= N; i++) {
            stream.print("        } else if( mat.numRows == " + i + " ) {\n" +
                    "            return det" + i + "(mat);            \n");
        }
        stream.print("        }\n" +
                "        \n" +
                "        throw new IllegalArgumentException(\"Not supported\");\n" +
                "    }\n\n");
    }

    protected String getInputValue( int element ) {
        return "mat.get( " + element + " )";
    }

    private void printFunction( int N ) {
        stream.print("    public static double det" + N + "( RowDMatrixD1 mat )\n" +
                "    {\n");

        printFunctionInner(N);

        stream.print("        return ret;\n");
        stream.print("    }\n");
        stream.print("\n");
    }

    public void printFunctionInner( int N ) {
        // extracts the first minor
        int M = N - 1;
        this.N = M;
        int matrix[] = new int[M*M];
        int index = 0;
        for (int i = 1; i <= M; i++) {
            int origIndex = i*N + 1;
            for (int j = 1; j <= M; j++, origIndex++, index++) {
                matrix[index] = index;
                stream.print("        double  " + a(index) + " = " + getInputValue(origIndex) + ";\n");
            }
        }

        stream.print("\n");
        stream.print("        double ret = 0;\n");
        stream.print("        ret += " + getInputValue(0) + " * (");
        minor(matrix, 0, M);
        stream.print(");\n");

        for (int minor = 2; minor <= N; minor++) {
            for (int i = 1; i <= M; i++) {
                index = (minor - 2) + (i - 1)*M;
                int origIndex = minor - 2 + i*N;
                stream.print("        " + a(index) + " = " + getInputValue(origIndex) + ";\n");
            }

            if (minor%2 == 0) {
                stream.print("        ret -= ");
            } else {
                stream.print("        ret += ");
            }
            stream.print(getInputValue(minor - 1) + " * (");
            minor(matrix, 0, M);
            stream.print(");\n");
        }
    }

    private void minor( int m[], int row, int N ) {
        if (N == 2) {
            stream.print(a(m[0]) + "*" + a(m[3]) + " - " + a(m[1]) + "*" + a(m[2]));
        } else {
            int M = N - 1;
            int d[] = new int[M*M];

            for (int i = 0; i < N; i++) {
                int index = 0;

                for (int j = 1; j < N; j++) {
                    for (int k = 0; k < N; k++) {
                        if (k != i) {
                            d[index++] = m[j*N + k];
                        }
                    }
                }

                int pow = i;

                if (pow%2 == 0)
                    stream.print(" + " + a(m[i]) + "*(");
                else
                    stream.print(" - " + a(m[i]) + "*(");

                minor(d, row + 1, M);

                stream.print(")");
            }
        }
    }

    private String a( int index ) {
        int i = index/N + 1;
        int j = index%N + 1;

        return "a" + i + "" + j;
    }

    public static void main( String args[] ) throws FileNotFoundException {
        GenerateDeterminantFromMinor gen = new GenerateDeterminantFromMinor("UnrolledDeterminantFromMinor.java");

        gen.createClass(6);
    }
}
