/*
 * Copyright 1997-2025 Optimatika
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package org.ojalgo.optimisation.convex;

import static org.ojalgo.function.constant.PrimitiveMath.ZERO;

import org.ojalgo.array.SparseArray;
import org.ojalgo.matrix.store.ElementsSupplier;
import org.ojalgo.matrix.store.MatrixStore;
import org.ojalgo.matrix.store.PhysicalStore;
import org.ojalgo.matrix.store.RowsSupplier;
import org.ojalgo.optimisation.ConstraintsMetaData;
import org.ojalgo.optimisation.ExpressionsBasedModel;
import org.ojalgo.optimisation.ModelEntity;
import org.ojalgo.structure.Access1D;
import org.ojalgo.structure.Access2D.RowView;
import org.ojalgo.type.keyvalue.EntryPair;

public final class ConvexData<N extends Comparable<N>> implements ExpressionsBasedModel.EntityMap {

    @FunctionalInterface
    interface ConvexDataFactory<N extends Comparable<N>> {

        ConvexData<N> newInstance(int nbVars, int nbEqus, int nbIneq);

    }

    private final RowsSupplier<N> myAE;
    private final RowsSupplier<N> myAI;
    private final PhysicalStore<N> myBE;
    private final PhysicalStore<N> myBI;
    private final ConstraintsMetaData myConstraintsMetaData;
    private final ConvexObjectiveFunction<N> myObjective;
    private final int[] myVariableIndices;

    ConvexData(final boolean inclMap, final PhysicalStore.Factory<N, ?> factory, final int nbVars, final int nbEqus, final int nbInes) {

        super();

        myObjective = new ConvexObjectiveFunction<>(factory, nbVars);

        myAE = factory.makeRowsSupplier(nbVars);
        myAE.addRows(nbEqus);
        myBE = factory.make(nbEqus, 1);

        myAI = factory.makeRowsSupplier(nbVars);
        myAI.addRows(nbInes);
        myBI = factory.make(nbInes, 1);

        myVariableIndices = new int[nbVars];
        myConstraintsMetaData = ConstraintsMetaData.newInstance(nbEqus + nbInes, inclMap);
    }

    @Override
    public int countAdditionalConstraints() {
        return 0;
    }

    @Override
    public int countConstraints() {
        return this.countEqualityConstraints() + this.countInequalityConstraints();
    }

    @Override
    public int countEqualityConstraints() {
        return myAE != null ? myAE.getRowDim() : 0;
    }

    @Override
    public int countInequalityConstraints() {
        return myAI != null ? myAI.getRowDim() : 0;
    }

    @Override
    public int countModelVariables() {
        return this.countVariables();
    }

    @Override
    public int countSlackVariables() {
        return 0;
    }

    @Override
    public int countVariables() {
        return myObjective.arity();
    }

    /**
     * Equality constraints body: [AE][X] == [BE]
     */
    public PhysicalStore<N> getAE() {
        return myAE.get();
    }

    /**
     * Equality constraints RHS: [AE][X] == [BE]
     */
    public MatrixStore<N> getBE() {
        return myBE;
    }

    public double getBE(final int row) {
        return myBE.doubleValue(row);
    }

    /**
     * Inequality constraints RHS: [AI][X] <= [BI]
     */
    public MatrixStore<N> getBI() {
        return myBI;
    }

    public double getBI(final int row) {
        return myBI.doubleValue(row);
    }

    @Override
    public EntryPair<ModelEntity<?>, ConstraintType> getConstraint(final int idc) {
        return myConstraintsMetaData.getEntry(idc);
    }

    public double getDensityAE() {
        return myAE != null ? myAE.density() : ZERO;
    }

    public double getDensityAI() {
        return myAI != null ? myAI.density() : ZERO;
    }

    public ConvexObjectiveFunction<N> getObjective() {
        return myObjective;
    }

    public RowView<N> getRowsAE() {
        return myAE.rows();
    }

    public RowView<N> getRowsAI() {
        return myAI.rows();
    }

    @Override
    public EntryPair<ModelEntity<?>, ConstraintType> getSlack(final int ids) {
        return null;
    }

    @Override
    public int indexOf(final int idm) {
        return myVariableIndices[idm];
    }

    @Override
    public boolean isNegated(final int idm) {
        return false;
    }

    void addObjective(final int row, final Comparable<?> value) {
        myObjective.linear().add(row, 0, value);
    }

    void addObjective(final int row, final double value) {
        myObjective.linear().add(row, 0, value);
    }

    void addObjective(final int row, final int col, final Comparable<?> value) {
        myObjective.quadratic().add(row, col, value);
    }

    void addObjective(final int row, final int col, final double value) {
        myObjective.quadratic().add(row, col, value);
    }

    /**
     * The density of the constraints body (AE and AI combined).
     */
    double density() {

        int nbEqus = this.countEqualityConstraints();
        int nbInes = this.countInequalityConstraints();

        if (nbEqus + nbInes == 0) {

            return ZERO;

        } else {

            double totalCount = ZERO;
            double nonzerosCount = ZERO;
            if (nbEqus > 0) {
                totalCount += myAE.count();
                nonzerosCount += myAE.countNonzeros();
            }
            if (nbInes > 0) {
                totalCount += myAI.count();
                nonzerosCount += myAI.countNonzeros();
            }
            return nonzerosCount / totalCount;
        }
    }

    SparseArray<N> getAE(final int row) {
        return myAE.getRow(row);
    }

    RowsSupplier<N> getAE(final int... rows) {
        return myAE.selectRows(rows);
    }

    /**
     * Inequality constraints body: [AI][X] <= [BI]
     */
    PhysicalStore<N> getAI() {
        return myAI.get();
    }

    SparseArray<N> getAI(final int row) {
        return myAI.getRow(row);
    }

    RowsSupplier<N> getAI(final int... rows) {
        return myAI.selectRows(rows);
    }

    ConstraintsMetaData getConstraintsMetaData() {
        return myConstraintsMetaData;
    }

    /**
     * Equality constraints body: [AE][X] == [BE]
     */
    ElementsSupplier<N> getSupplierAE() {
        return myAE;
    }

    /**
     * Inequality constraints body: [AI][X] == [BI]
     */
    ElementsSupplier<N> getSupplierAI() {
        return myAI;
    }

    void reset() {
        // TODO Auto-generated method stub
    }

    void setAE(final int row, final int col, final Comparable<?> value) {
        myAE.getRow(row).set(col, value);
    }

    void setAE(final int row, final int col, final double value) {
        myAE.getRow(row).set(col, value);
    }

    void setAI(final int row, final int col, final Comparable<?> value) {
        myAI.getRow(row).set(col, value);
    }

    void setAI(final int row, final int col, final double value) {
        myAI.getRow(row).set(col, value);
    }

    void setBE(final int row, final double value) {
        myBE.set(row, 0, value);
    }

    void setBE(final int row, final ModelEntity<?> entity, final ConstraintType type, final Comparable<?> value, final boolean negated) {
        myBE.set(row, 0, value);
        myConstraintsMetaData.setEntry(row, entity, type, negated);
    }

    void setBI(final int row, final double value) {
        myBI.set(row, 0, value);
    }

    void setBI(final int row, final ModelEntity<?> entity, final ConstraintType type, final Comparable<?> value, final boolean negated) {
        myBI.set(row, 0, value);
        myConstraintsMetaData.setEntry(myBE.getRowDim() + row, entity, type, negated);
    }

    void setEntry(final int row, final ModelEntity<?> entity, final ConstraintType type, final boolean neg) {
        myConstraintsMetaData.setEntry(row, entity, type, neg);
    }

    void setObjective(final int row, final Comparable<?> value) {
        myObjective.linear().set(row, 0, value);
    }

    void setObjective(final int row, final double value) {
        myObjective.linear().set(row, 0, value);
    }

    void setObjective(final int row, final int col, final Comparable<?> value) {
        myObjective.quadratic().set(row, col, value);
    }

    void setObjective(final int row, final int col, final double value) {
        myObjective.quadratic().set(row, col, value);
    }

    void setVariableIndices(final int indexInSolver, final int indexInModel) {
        myVariableIndices[indexInSolver] = indexInModel;
    }

    void update(final Access1D<?> mtrxC, final Access1D<?> mtrxBE, final Access1D<?> mtrxBI) {
        myObjective.linear().fillMatching(mtrxC);
        myBE.fillMatching(mtrxBE);
        myBI.fillMatching(mtrxBI);
    }

}
