/* ====================================================================
   Licensed to the Apache Software Foundation (ASF) under one or more
   contributor license agreements.  See the NOTICE file distributed with
   this work for additional information regarding copyright ownership.
   The ASF licenses this file to You 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.apache.poi.ss.formula.functions;

import org.apache.poi.ss.SpreadsheetVersion;
import org.apache.poi.ss.formula.TwoDEval;
import org.apache.poi.ss.formula.eval.AreaEval;
import org.apache.poi.ss.formula.eval.AreaEvalBase;
import org.apache.poi.ss.formula.eval.NumberEval;
import org.apache.poi.ss.formula.eval.RefEval;
import org.apache.poi.ss.formula.eval.RefEvalBase;
import org.apache.poi.ss.formula.eval.ValueEval;
import org.apache.poi.ss.formula.ptg.AreaI;
import org.apache.poi.ss.formula.ptg.AreaPtg;
import org.apache.poi.ss.formula.ptg.Ref3DPtg;
import org.apache.poi.ss.formula.ptg.RefPtg;
import org.apache.poi.ss.util.AreaReference;

/**
 * Test helper class for creating mock {@code Eval} objects
 */
public final class EvalFactory {

    private EvalFactory() {
        // no instances of this class
    }

    /**
     * Creates a dummy AreaEval
     * @param values empty (<code>null</code>) entries in this array will be converted to NumberEval.ZERO
     */
    public static AreaEval createAreaEval(String areaRefStr, ValueEval[] values) {
        AreaPtg areaPtg = new AreaPtg(new AreaReference(areaRefStr, SpreadsheetVersion.EXCEL2007));
        return createAreaEval(areaPtg, values);
    }

    /**
     * Creates a dummy AreaEval
     * @param values empty (<code>null</code>) entries in this array will be converted to NumberEval.ZERO
     */
    public static AreaEval createAreaEval(AreaPtg areaPtg, ValueEval[] values) {
        int nCols = areaPtg.getLastColumn() - areaPtg.getFirstColumn() + 1;
        int nRows = areaPtg.getLastRow() - areaPtg.getFirstRow() + 1;
        int nExpected = nRows * nCols;
        if (values.length != nExpected) {
            throw new RuntimeException("Expected " + nExpected + " values but got " + values.length);
        }
        for (int i = 0; i < nExpected; i++) {
            if (values[i] == null) {
                values[i] = NumberEval.ZERO;
            }
        }
        return new MockAreaEval(areaPtg, values);
    }

    /**
     * Creates a single RefEval (with value zero)
     */
    public static RefEval createRefEval(String refStr) {
        return createRefEval(refStr, NumberEval.ZERO);
    }
    public static RefEval createRefEval(String refStr, ValueEval value) {
        return new MockRefEval(new RefPtg(refStr), value);
    }

    private static final class MockAreaEval extends AreaEvalBase {
        private final ValueEval[] _values;
        public MockAreaEval(AreaI areaPtg, ValueEval[] values) {
            super(areaPtg);
            _values = values;
        }
        private MockAreaEval(int firstRow, int firstColumn, int lastRow, int lastColumn, ValueEval[] values) {
            super(firstRow, firstColumn, lastRow, lastColumn);
            _values = values;
        }
        @Override
        public ValueEval getRelativeValue(int relativeRowIndex, int relativeColumnIndex) {
            return getRelativeValue(-1, relativeRowIndex, relativeColumnIndex);
        }
        @Override
        public ValueEval getRelativeValue(int sheetIndex, int relativeRowIndex, int relativeColumnIndex) {
            if (relativeRowIndex < 0 || relativeRowIndex >=getHeight()) {
                throw new IllegalArgumentException("row index out of range");
            }
            int width = getWidth();
            if (relativeColumnIndex < 0 || relativeColumnIndex >=width) {
                throw new IllegalArgumentException("column index out of range");
            }
            int oneDimensionalIndex = relativeRowIndex * width + relativeColumnIndex;
            return _values[oneDimensionalIndex];
        }
        @Override
        public AreaEval offset(int relFirstRowIx, int relLastRowIx, int relFirstColIx, int relLastColIx) {
            if (relFirstRowIx < 0 || relFirstColIx < 0
                    || relLastRowIx >= getHeight() || relLastColIx >= getWidth()) {
                throw new RuntimeException("Operation not implemented on this mock object");
            }

            if (relFirstRowIx == 0 && relFirstColIx == 0
                    && relLastRowIx == getHeight()-1 && relLastColIx == getWidth()-1) {
                return this;
            }
            ValueEval[] values = transpose(_values, getWidth(), relFirstRowIx, relLastRowIx, relFirstColIx, relLastColIx);
            return new MockAreaEval(getFirstRow() + relFirstRowIx, getFirstColumn() + relFirstColIx,
                    getFirstRow() + relLastRowIx, getFirstColumn() + relLastColIx, values);
        }
        private static ValueEval[] transpose(ValueEval[] srcValues, int srcWidth,
                int relFirstRowIx, int relLastRowIx,
                int relFirstColIx, int relLastColIx) {
            int height = relLastRowIx - relFirstRowIx + 1;
            int width = relLastColIx - relFirstColIx + 1;
            ValueEval[] result = new ValueEval[height * width];
            for (int r=0; r<height; r++) {
                int srcRowIx = r + relFirstRowIx;
                for (int c=0; c<width; c++) {
                    int srcColIx = c + relFirstColIx;
                    int destIx = r * width + c;
                    int srcIx = srcRowIx * srcWidth + srcColIx;
                    result[destIx] = srcValues[srcIx];
                }
            }
            return result;
        }
        @Override
        public TwoDEval getRow(int rowIndex) {
            if (rowIndex >= getHeight()) {
                throw new IllegalArgumentException("Invalid rowIndex " + rowIndex
                        + ".  Allowable range is (0.." + getHeight() + ").");
            }
            ValueEval[] values = new ValueEval[getWidth()];
            for (int i = 0; i < values.length; i++) {
                values[i] = getRelativeValue(rowIndex, i);
            }
            return new MockAreaEval(rowIndex, getFirstColumn(), rowIndex, getLastColumn(), values);
        }
        @Override
        public TwoDEval getColumn(int columnIndex) {
            if (columnIndex >= getWidth()) {
                throw new IllegalArgumentException("Invalid columnIndex " + columnIndex
                        + ".  Allowable range is (0.." + getWidth() + ").");
            }
            ValueEval[] values = new ValueEval[getHeight()];
            for (int i = 0; i < values.length; i++) {
                values[i] = getRelativeValue(i, columnIndex);
            }
            return new MockAreaEval(getFirstRow(), columnIndex, getLastRow(), columnIndex, values);
        }
    }

    private static final class MockRefEval extends RefEvalBase {
        private final ValueEval _value;
        public MockRefEval(RefPtg ptg, ValueEval value) {
            super(-1, -1, ptg.getRow(), ptg.getColumn());
            _value = value;
        }
        public MockRefEval(Ref3DPtg ptg, ValueEval value) {
            super(-1, -1, ptg.getRow(), ptg.getColumn());
            _value = value;
        }
        @Override
        public ValueEval getInnerValueEval(int sheetIndex) {
            return _value;
        }
        @Override
        public AreaEval offset(int relFirstRowIx, int relLastRowIx, int relFirstColIx, int relLastColIx) {
            throw new RuntimeException("Operation not implemented on this mock object");
        }
    }
}
