/*
 * 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.sysds.runtime.data;

import org.apache.commons.lang3.NotImplementedException;
import org.apache.sysds.runtime.DMLRuntimeException;
import org.apache.sysds.runtime.meta.DataCharacteristics;

import java.util.Arrays;
import java.util.List;

public class LibTensorReorg {
	//allow shallow dense/sparse copy for unchanged data (which is
	//safe due to copy-on-write and safe update-in-place handling)
	private static final boolean SHALLOW_COPY_REORG = true;

	private LibTensorReorg() {
		//prevent instantiation via private constructor
	}

	/**
	 * CP reshape operation (single input, single output tensor)
	 *
	 * @param in input tensor
	 * @param out output tensor
	 * @param dims dimensions
	 * @return output tensor
	 */
	public static BasicTensorBlock reshape(BasicTensorBlock in, BasicTensorBlock out, int[] dims) {
		long length = 1;
		for (int dim : dims) {
			length *= dim;
		}
		int[] inDims = in.getDims();
		//check validity
		if(in.getLength() != length) {
			throw new DMLRuntimeException("Reshape tensor requires consistent numbers of input/output cells (" +
					Arrays.toString(inDims) + ", " + Arrays.toString(dims) + ").");
		}

		//check for same dimensions
		if( Arrays.equals(inDims, dims)) {
			//copy incl dims, nnz
			if( SHALLOW_COPY_REORG )
				out.copyShallow(in);
			else // TODO deep copy
				out.copy(in);
			return out;
		}

		// TODO eval sparse output
		out._sparse = false;

		//set output dimensions
		out._dims = dims;
		out._nnz = in._nnz;

		//core reshape (sparse or dense)
		if(!in.isSparse() && !out.isSparse())
			reshapeDense(in, out, dims);
		else if(in.isSparse() && out.isSparse())
			throw new NotImplementedException();
		else if(in.isSparse())
			throw new NotImplementedException();
		else
			throw new NotImplementedException();

		return out;
	}

	private static void reshapeDense(BasicTensorBlock in, BasicTensorBlock out, int[] dims) {
		//reshape empty block
		if( in._denseBlock == null )
			return;

		//shallow dense by-row reshape (w/o result allocation)
		if( SHALLOW_COPY_REORG && in._denseBlock.numBlocks()==1 ) {
			//since the physical representation of dense matrices is always the same,
			//we don't need to create a copy, given our copy on write semantics.
			//however, note that with update in-place this would be an invalid optimization
			DenseBlock denseBlock = in._denseBlock;
			if (denseBlock instanceof DenseBlockBool) {
				DenseBlockBool specificBlock = (DenseBlockBool) denseBlock;
				out._denseBlock = DenseBlockFactory.createDenseBlock(specificBlock.getData(), dims);
			} else if (denseBlock instanceof DenseBlockString) {
				DenseBlockString specificBlock = (DenseBlockString) denseBlock;
				out._denseBlock = DenseBlockFactory.createDenseBlock(specificBlock.getData(), dims);
			}else if (denseBlock instanceof DenseBlockFP64) {
				out._denseBlock = DenseBlockFactory.createDenseBlock(in._denseBlock.valuesAt(0), dims);
			} else if (denseBlock instanceof DenseBlockFP32) {
				DenseBlockFP32 specificBlock = (DenseBlockFP32) denseBlock;
				out._denseBlock = DenseBlockFactory.createDenseBlock(specificBlock.getData(), dims);
			} else if (denseBlock instanceof DenseBlockInt64) {
				DenseBlockInt64 specificBlock = (DenseBlockInt64) denseBlock;
				out._denseBlock = DenseBlockFactory.createDenseBlock(specificBlock.getData(), dims);
			} else if (denseBlock instanceof DenseBlockInt32) {
				DenseBlockInt32 specificBlock = (DenseBlockInt32) denseBlock;
				out._denseBlock = DenseBlockFactory.createDenseBlock(specificBlock.getData(), dims);
			}
			return;
		}
		out.set(in);
	}

	/**
	 * MR/SPARK reshape interface - for reshape we cannot view blocks independently, and hence,
	 * there are different CP and MR interfaces.
	 *
	 * @param in indexed tensor block
	 * @param mcIn input tensor characteristics
	 * @param mcOut output tensor characteristics
	 * @param rowwise if true, reshape by row
	 * @param outputEmptyBlocks output blocks with nnz=0
	 * @return list of indexed tensor block
	 */
	public static List<IndexedTensorBlock> reshape(IndexedTensorBlock in, DataCharacteristics mcIn,
	                                               DataCharacteristics mcOut, boolean rowwise, boolean outputEmptyBlocks ) {
		throw new DMLRuntimeException("Spark reshape not implemented for tensors.");
	}
}
