/*******************************************************************************
 * Copyright (c) 2004, 2005 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/

package org.eclipse.debug.internal.ui.views.memory.renderings;

import java.math.BigInteger;

import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.model.IMemoryBlock;
import org.eclipse.debug.core.model.IMemoryBlockExtension;
import org.eclipse.debug.core.model.MemoryByte;
import org.eclipse.debug.internal.ui.DebugUIMessages;
import org.eclipse.debug.internal.ui.DebugUIPlugin;
import org.eclipse.debug.internal.ui.views.memory.MemoryViewUtil;
import org.eclipse.debug.ui.IDebugUIConstants;
import org.eclipse.debug.ui.memory.AbstractTableRendering;
import org.eclipse.jface.viewers.ICellModifier;
import org.eclipse.swt.widgets.TableItem;

/**
 * @since 3.1
 */
public class TableRenderingCellModifier implements ICellModifier {

    private boolean editActionInvoked = false;

    private AbstractTableRendering fRendering;

    public TableRenderingCellModifier(AbstractTableRendering rendering) {
        fRendering = rendering;
    }

    /*
     * (non-Javadoc)
     *
     * @see org.eclipse.jface.viewers.ICellModifier#canModify(java.lang.Object,
     *      java.lang.String)
     */
    @Override
	public boolean canModify(Object element, String property) {
        boolean canModify = true;
        try {
            if (!(element instanceof TableRenderingLine))
                return false;

            if (!editActionInvoked)
                return false;

            if (fRendering == null)
                return false;

            if (fRendering.getMemoryBlock().supportsValueModification() == false) {
                return false;
            }

            TableRenderingLine line = (TableRenderingLine) element;
            if (TableRenderingLine.P_ADDRESS.equals(property)) {
                return false;
            }

            // property is stored as number of addressable unit away from the
            // line address
            // to calculate offset to the memory line array, offset =
            // numberofAddressableUnit * addressableSize
            int addressableSize = getAddressableSize();

            int offset = Integer.valueOf(property, 16).intValue() * addressableSize;
            int end = offset + fRendering.getBytesPerColumn();

            for (int i = offset; i < end; i++) {
                MemoryByte oneByte = line.getByte(i);

                if (!oneByte.isWritable()) {
                    canModify = false;
                }
            }
            return canModify;
        } catch (NumberFormatException e) {
            canModify = false;
            return canModify;
        }
    }

    /**
     * @return
     */
    private int getAddressableSize() {
        int addressableSize = fRendering.getAddressableSize();
        if (addressableSize < 1)
            addressableSize = 1;
        return addressableSize;
    }

    /*
     * (non-Javadoc)
     *
     * @see org.eclipse.jface.viewers.ICellModifier#getValue(java.lang.Object,
     *      java.lang.String)
     */
    @Override
	public Object getValue(Object element, String property) {
        // give back the value of the column

        if (!(element instanceof TableRenderingLine))
            return null;

        TableRenderingLine line = (TableRenderingLine) element;
        try {
            if (TableRenderingLine.P_ADDRESS.equals(property))
                return line.getAddress();

            int offset = Integer.valueOf(property, 16).intValue() * getAddressableSize();
            int end = offset + fRendering.getBytesPerColumn();

            // Ask for label provider
            MemoryByte[] memory = line.getBytes(offset, end);

            if (line.isAvailable(offset, end)) {
                // ask the renderer for a string representation of the bytes
                offset = Integer.valueOf(property, 16).intValue();

                BigInteger address = new BigInteger(((TableRenderingLine) element).getAddress(), 16);
                address = address.add(BigInteger.valueOf(offset));

                return fRendering.getString(fRendering.getRenderingId(), address, memory);
            }
            // if the range is not available, just return padded string
            return getPaddedString(offset, end);
        } catch (NumberFormatException e) {
            return "00"; //$NON-NLS-1$
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see org.eclipse.jface.viewers.ICellModifier#modify(java.lang.Object,
     *      java.lang.String, java.lang.Object)
     */
    @Override
	public void modify(Object element, String property, Object value) {
        TableRenderingLine line = null;
        if (!(element instanceof TableRenderingLine)) {
            line = (TableRenderingLine) ((TableItem) element).getData();
        } else {
            line = (TableRenderingLine) element;
        }

        // validate data
        if (!(value instanceof String))
            return;

        try {
            // calculate offset to update
            IMemoryBlock memoryBlk = fRendering.getMemoryBlock();

            int lineOffset = Integer.valueOf(property, 16).intValue();

            // this offset is number of addressable unit from the line address
            BigInteger offset = getOffset(memoryBlk, line.getAddress(), lineOffset);

            byte[] bytes = null;

            String oldValue = (String) getValue(line, property);

            if (!oldValue.equals(value)) {

                // property is number of addressable unit from line address
                // to calculate proper offset in the memoryViewLine's array
                // offset = numberOfAddressableUnit * addressableSize
                int offsetToLine = Integer.valueOf(property, 16).intValue() * getAddressableSize();
                int end = offsetToLine + fRendering.getBytesPerColumn();

                MemoryByte[] oldArray = line.getBytes(offsetToLine, end);

                BigInteger address = new BigInteger(line.getAddress(), 16);
                address = address.add(BigInteger.valueOf(offsetToLine));

                bytes = fRendering.getBytes(fRendering.getRenderingId(), address, oldArray, (String) value);

                if (bytes == null)
                    return;

                if (bytes.length == 0)
                    return;

                if (bytes.length <= oldArray.length) {
                    boolean changed = false;
                    // check that the bytes returned has actually changed
                    for (int i = 0; i < bytes.length; i++) {
                        if (bytes[i] != oldArray[i].getValue()) {
                            changed = true;
                            break;
                        }
                    }
                    if (!changed)
                        return;
                }
            } else {
                // return if value has not changed
                return;
            }

            if (memoryBlk instanceof IMemoryBlockExtension)
                ((IMemoryBlockExtension) memoryBlk).setValue(offset, bytes);
            else
                memoryBlk.setValue(offset.longValue(), bytes);
        } catch (DebugException e) {
            MemoryViewUtil.openError(DebugUIMessages.MemoryViewCellModifier_failure_title, DebugUIMessages.MemoryViewCellModifier_failed, e);
        } catch (NumberFormatException e) {
            MemoryViewUtil.openError(DebugUIMessages.MemoryViewCellModifier_failure_title, DebugUIMessages.MemoryViewCellModifier_failed + "\n" + DebugUIMessages.MemoryViewCellModifier_data_is_invalid, null); //$NON-NLS-1$
        }

    }

    private BigInteger getOffset(IMemoryBlock memory, String lineAddress, int lineOffset) throws DebugException {

        BigInteger lineAddr = new BigInteger(lineAddress, 16);
        BigInteger memoryAddr;

        if (memory instanceof IMemoryBlockExtension) {
            memoryAddr = ((IMemoryBlockExtension) memory).getBigBaseAddress();
        } else {
            memoryAddr = BigInteger.valueOf(memory.getStartAddress());
        }

        if (memoryAddr == null)
            memoryAddr = new BigInteger("0"); //$NON-NLS-1$

        return lineAddr.subtract(memoryAddr).add(BigInteger.valueOf(lineOffset));
    }

    /**
     * @param editActionInvoked
     *            The editActionInvoked to set.
     */
    public void setEditActionInvoked(boolean editActionInvoked) {
        this.editActionInvoked = editActionInvoked;
    }

    /**
     * @param start
     * @param end
     * @return padded string
     */
    public String getPaddedString(int start, int end) {
        StringBuffer buf = new StringBuffer();
        String paddedStr = DebugUIPlugin.getDefault().getPreferenceStore().getString(IDebugUIConstants.PREF_PADDED_STR);
        for (int i = start; i < end; i++) {
            buf.append(paddedStr);
        }
        return buf.toString();
    }

}
