/*
 * Copyright (c) 2009-2014, 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.alg.dense.mult;

import org.ejml.alg.generic.CodeGeneratorMisc;

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

/**
 * @author Peter Abeles
 */
public class GenerateCMatrixMatrixMult {
    PrintStream stream;

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

    public void createClass() {
        String preamble = CodeGeneratorMisc.COPYRIGHT +
                "package org.ejml.alg.dense.mult;\n" +
                "\n" +
                "import org.ejml.data.CDenseMatrix64F;\n" +
                "import org.ejml.ops.CCommonOps;\n" +
                "\n" +
                "/**\n" +
                " * <p>Matrix multiplication routines for complex dense matrices in a row-major format.</p>\n" +
                " *\n" +
                " * <p>\n" +
                " * DO NOT MODIFY! Auto generated by {@link org.ejml.alg.dense.mult.GenerateCMatrixMatrixMult}.\n" +
                " * </p>\n" +
                " *\n" +
                " * @author Peter Abeles\n" +
                " */\n" +
                "public class CMatrixMatrixMult {\n";

        stream.print(preamble);

        for( int i = 0; i < 2; i++ ) {
            boolean alpha = i == 1;
            for( int j = 0; j < 2; j++ ) {
                boolean add = j == 1;
                printMult_reroder(alpha,add);
                stream.print("\n");
                printMult_small(alpha,add);
                stream.print("\n");
            }
        }
        stream.print("}\n");
    }

    public void printMult_reroder( boolean alpha , boolean add ) {
        String header,valLine;

        header = makeHeader("mult","reorder",add,alpha, false, false,false);

        String tempVars = "";

        if( alpha ) {
            tempVars = "        double realTmp,imgTmp;";
            valLine = "            realTmp = a.data[indexA++];\n" +
                      "            imgTmp = a.data[indexA++];\n" +
                      "            realA = realAlpha*realTmp - imgAlpha*imgTmp;\n" +
                      "            imgA = realAlpha*imgTmp + imgAlpha*realTmp;\n";
        } else {
            valLine = "            realA = a.data[indexA++];\n" +
                      "            imgA = a.data[indexA++];\n";
        }

        String assignment = add ? "+=" : "=";

        String foo = header + makeBoundsCheck(false,false, null)+handleZeros(add) +
                "        double realA,imgA;\n" +
                tempVars +
                "\n" +
                "        int indexCbase= 0;\n" +
                "        int strideA = a.getRowStride();\n" +
                "        int strideB = b.getRowStride();\n" +
                "        int strideC = c.getRowStride();\n" +
                "        int endOfKLoop = b.numRows*strideB;\n" +
                "\n" +
                "        for( int i = 0; i < a.numRows; i++ ) {\n" +
                "            int indexA = i*strideA;\n" +
                "\n" +
                "            // need to assign c.data to a value initially\n" +
                "            int indexB = 0;\n" +
                "            int indexC = indexCbase;\n" +
                "            int end = indexB + strideB;\n" +
                "\n" +
                valLine +
                "\n" +
                "            while( indexB < end ) {\n" +
                "                double realB = b.data[indexB++];\n" +
                "                double imgB = b.data[indexB++];\n" +
                "\n" +
                "                c.data[indexC++] "+assignment+" realA*realB - imgA*imgB;\n" +
                "                c.data[indexC++] "+assignment+" realA*imgB + imgA*realB;\n" +
                "            }\n" +
                "\n" +
                "            // now add to it\n" +
                "            while( indexB != endOfKLoop ) { // k loop\n" +
                "                indexC = indexCbase;\n" +
                "                end = indexB + strideB;\n" +
                "\n" +
                valLine +
                "\n" +
                "                while( indexB < end ) { // j loop\n" +
                "                    double realB = b.data[indexB++];\n" +
                "                    double imgB = b.data[indexB++];\n" +
                "\n" +
                "                    c.data[indexC++] += realA*realB - imgA*imgB;\n" +
                "                    c.data[indexC++] += realA*imgB + imgA*realB;\n" +
                "                }\n" +
                "            }\n" +
                "            indexCbase += strideC;\n" +
                "        }\n" +
                "    }\n\n";

        stream.print(foo);
    }

    public void printMult_small( boolean alpha , boolean add ) {
        String header,valLine;

        header = makeHeader("mult","small",add,alpha, false, false,false);

        String assignment = add ? "+=" : "=";

        if( alpha ) {
            valLine = "                c.data[cIndex++] "+assignment+" realAlpha*realTotal - imgAlpha*imgTotal;\n" +
                      "                c.data[cIndex++] "+assignment+" realAlpha*imgTotal + imgAlpha*realTotal;\n";
        } else {
            valLine = "                c.data[cIndex++] "+assignment+" realTotal;\n" +
                      "                c.data[cIndex++] "+assignment+" imgTotal;\n";
        }

        String foo =
                header + makeBoundsCheck(false,false, null)+
                        "        int aIndexStart = 0;\n" +
                        "        int cIndex = 0;\n" +
                        "\n" +
                        "        int strideA = a.getRowStride();\n" +
                        "        int strideB = b.getRowStride();\n" +
                        "\n" +
                        "        for( int i = 0; i < a.numRows; i++ ) {\n" +
                        "            for( int j = 0; j < b.numCols; j++ ) {\n" +
                        "                double realTotal = 0;\n" +
                        "                double imgTotal = 0;\n" +
                        "\n" +
                        "                int indexA = aIndexStart;\n" +
                        "                int indexB = j*2;\n" +
                        "                int end = indexA + strideA;\n" +
                        "                while( indexA < end ) {\n" +
                        "                    double realA = a.data[indexA++];\n" +
                        "                    double imgA = a.data[indexA++];\n" +
                        "\n" +
                        "                    double realB = b.data[indexB];\n" +
                        "                    double imgB = b.data[indexB+1];\n" +
                        "\n" +
                        "                    realTotal += realA*realB - imgA*imgB;\n" +
                        "                    imgTotal += realA*imgB + imgA*realB;\n" +
                        "\n" +
                        "                    indexB += strideB;\n" +
                        "                }\n" +
                        "\n" +
                        valLine +
                        "            }\n" +
                        "            aIndexStart += strideA;\n" +
                        "        }\n" +
                        "    }\n\n";

        stream.print(foo);
    }

    private String makeBoundsCheck(boolean tranA, boolean tranB, String auxLength)
    {
        String a_numCols = tranA ? "a.numRows" : "a.numCols";
        String a_numRows = tranA ? "a.numCols" : "a.numRows";
        String b_numCols = tranB ? "b.numRows" : "b.numCols";
        String b_numRows = tranB ? "b.numCols" : "b.numRows";

        String ret =
                "        if( a == c || b == c )\n" +
                        "            throw new IllegalArgumentException(\"Neither 'a' or 'b' can be the same matrix as 'c'\");\n"+
                        "        else if( "+a_numCols+" != "+b_numRows+" ) {\n" +
                        "            throw new MatrixDimensionException(\"The 'a' and 'b' matrices do not have compatible dimensions\");\n" +
                        "        } else if( "+a_numRows+" != c.numRows || "+b_numCols+" != c.numCols ) {\n" +
                        "            throw new MatrixDimensionException(\"The results matrix does not have the desired dimensions\");\n" +
                        "        }\n" +
                        "\n";

        if( auxLength != null ) {
            ret += "        if( aux == null ) aux = new double[ "+auxLength+" ];\n\n";
        }

        return ret;
    }

    private String handleZeros( boolean add ) {

        String fill = add ? "" : "            CCommonOps.fill(c,0,0);\n";

        String ret =
                "        if( a.numCols == 0 || a.numRows == 0 ) {\n" +
                        fill +
                        "            return;\n" +
                        "        }\n";
        return ret;
    }

    private String makeHeader(String nameOp, String variant,
                              boolean add, boolean hasAlpha, boolean hasAux,
                              boolean tranA, boolean tranB)
    {
        if( add ) nameOp += "Add";

        // make the op name
        if( tranA && tranB ) {
            nameOp += "TransAB";
        } else if( tranA ) {
            nameOp += "TransA";
        } else if( tranB ) {
            nameOp += "TransB";
        }

        String ret = "    public static void "+nameOp;

        if( variant != null ) ret += "_"+variant+"( ";
        else ret += "( ";

        if( hasAlpha ) ret += "double realAlpha , double imgAlpha , ";

        if( hasAux ) {
            ret += "CDenseMatrix64F a , CDenseMatrix64F b , CDenseMatrix64F c , double []aux )\n";
        } else {
            ret += "CDenseMatrix64F a , CDenseMatrix64F b , CDenseMatrix64F c )\n";
        }

        ret += "    {\n";

        return ret;
    }

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

        gen.createClass();
    }
}
