/*******************************************************************************
 * Copyright (c) 2008, 2015 Wind River Systems 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:
 *     Wind River Systems - initial API and implementation
 *******************************************************************************/
package org.eclipse.cdt.examples.dsf.pda.service;

import java.math.BigInteger;
import java.util.Hashtable;
import java.util.Map;

import org.eclipse.cdt.dsf.concurrent.DataRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.RequestMonitor;
import org.eclipse.cdt.dsf.datamodel.AbstractDMContext;
import org.eclipse.cdt.dsf.datamodel.AbstractDMEvent;
import org.eclipse.cdt.dsf.datamodel.DMContexts;
import org.eclipse.cdt.dsf.datamodel.IDMContext;
import org.eclipse.cdt.dsf.debug.service.ICachingService;
import org.eclipse.cdt.dsf.debug.service.IExpressions.IExpressionDMContext;
import org.eclipse.cdt.dsf.debug.service.IRegisters;
import org.eclipse.cdt.dsf.debug.service.IRunControl;
import org.eclipse.cdt.dsf.debug.service.command.CommandCache;
import org.eclipse.cdt.dsf.debug.service.command.IEventListener;
import org.eclipse.cdt.dsf.service.AbstractDsfService;
import org.eclipse.cdt.dsf.service.DsfServiceEventHandler;
import org.eclipse.cdt.dsf.service.DsfSession;
import org.eclipse.cdt.examples.dsf.pda.PDAPlugin;
import org.eclipse.cdt.examples.dsf.pda.service.commands.PDABitField;
import org.eclipse.cdt.examples.dsf.pda.service.commands.PDAGroupsCommand;
import org.eclipse.cdt.examples.dsf.pda.service.commands.PDAListResult;
import org.eclipse.cdt.examples.dsf.pda.service.commands.PDARegister;
import org.eclipse.cdt.examples.dsf.pda.service.commands.PDARegistersCommand;
import org.eclipse.cdt.examples.dsf.pda.service.commands.PDARegistersCommandResult;
import org.osgi.framework.BundleContext;

/**
 *
 */
public class PDARegisters extends AbstractDsfService implements IRegisters, IEventListener, ICachingService {

	private static class RegisterGroupDMContext extends AbstractDMContext implements IRegisterGroupDMContext {
		final private String fName;

		public RegisterGroupDMContext(String sessionId, PDAVirtualMachineDMContext dmc, String groupName) {
			super(sessionId, new IDMContext[] { dmc });
			fName = groupName;
		}

		@Override
		public boolean equals(Object other) {
			return ((super.baseEquals(other)) && (((RegisterGroupDMContext) other).fName.equals(fName)));
		}

		@Override
		public int hashCode() {
			return super.baseHashCode() + fName.hashCode();
		}

		@Override
		public String toString() {
			return baseToString() + ".group[" + fName + "]"; //$NON-NLS-1$//$NON-NLS-2$
		}
	}

	private static class RegisterDMContext extends AbstractDMContext implements IRegisterDMContext {
		final private PDARegister fRegister;

		public RegisterDMContext(String sessionId, PDAThreadDMContext thread, RegisterGroupDMContext group,
				PDARegister reg) {
			super(sessionId, new IDMContext[] { thread, group });
			fRegister = reg;
		}

		@Override
		public boolean equals(Object other) {
			return ((super.baseEquals(other)) && (((RegisterDMContext) other).fRegister.fName.equals(fRegister.fName)));
		}

		@Override
		public int hashCode() {
			return super.baseHashCode() + fRegister.fName.hashCode();
		}

		@Override
		public String toString() {
			return baseToString() + ".register[" + fRegister.fName + "]"; //$NON-NLS-1$//$NON-NLS-2$
		}
	}

	/*
	 * Support class used to construct BitField DMCs.
	 */
	private static class BitFieldDMContext extends AbstractDMContext implements IBitFieldDMContext {

		final private PDABitField fBitField;

		public BitFieldDMContext(String sessionId, RegisterDMContext reg, PDABitField bitField) {
			super(sessionId, new IDMContext[] { reg });

			fBitField = bitField;
		}

		/*
		 *  Required common manipulation routines.
		 */
		@Override
		public boolean equals(Object other) {
			if (other instanceof BitFieldDMContext) {
				BitFieldDMContext dmc = (BitFieldDMContext) other;
				return ((super.baseEquals(other)) && (dmc.fBitField.fName.equals(fBitField.fName)));
			} else {
				return false;
			}
		}

		@Override
		public int hashCode() {
			return super.baseHashCode() + fBitField.fName.hashCode();
		}

		@Override
		public String toString() {
			return baseToString() + ".bitfield[" + fBitField.fName + "]"; //$NON-NLS-1$//$NON-NLS-2$
		}
	}

	private static class RegisterGroupDMData implements IRegisterGroupDMData {

		final private String fName;

		public RegisterGroupDMData(String name) {
			fName = name;
		}

		@Override
		public String getName() {
			return fName;
		}

		@Override
		public String getDescription() {
			return "Description of the " + fName + " register group";
		}
	}

	private static class RegisterDMData implements IRegisterDMData {

		final private PDARegister fRegister;

		public RegisterDMData(PDARegister reg) {
			fRegister = reg;
		}

		@Override
		public boolean isReadable() {
			return true;
		}

		@Override
		public boolean isReadOnce() {
			return false;
		}

		@Override
		public boolean isWriteable() {
			return fRegister.fWritable;
		}

		@Override
		public boolean isWriteOnce() {
			return false;
		}

		@Override
		public boolean hasSideEffects() {
			return false;
		}

		@Override
		public boolean isVolatile() {
			return true;
		}

		@Override
		public boolean isFloat() {
			return false;
		}

		@Override
		public String getName() {
			return fRegister.fName;
		}

		@Override
		public String getDescription() {
			return "Description of the " + fRegister.fName + " register";
		}
	}

	private static class Mnemonic implements IMnemonic {
		Mnemonic(String name, String value, int numBits) {
			fName = name;
			fValue = new BigInteger(value);
			fNumBits = numBits;
		}

		final private String fName;
		final private BigInteger fValue;
		final private int fNumBits;

		@Override
		public String getShortName() {
			return fName;
		}

		@Override
		public String getLongName() {
			return fName;
		}

		public BigInteger getValue() {
			return fValue;
		}

		public int getBitCount() {
			return fNumBits;
		}

		@Override
		public boolean equals(Object element) {
			if (element instanceof Mnemonic) {
				Mnemonic mnem = (Mnemonic) element;
				return (mnem.fName.equals(fName)) && (mnem.fValue.equals(fValue)) && (mnem.fNumBits == fNumBits);
			}
			return false;
		}
	}

	private class BitFieldDMData implements IBitFieldDMData {

		final private PDABitField fBitField;
		final private IBitGroup[] fBitGroups;
		final private Mnemonic[] fMnemonics;
		final private BigInteger fValue;
		final private Mnemonic fMnemonicValue;

		public BitFieldDMData(PDABitField bitField, String value) {
			fBitField = bitField;
			fValue = new BigInteger(value);

			fBitGroups = new IBitGroup[] { new IBitGroup() {
				@Override
				public int startBit() {
					return fBitField.fOffset;
				}

				@Override
				public int bitCount() {
					return fBitField.fCount;
				}
			} };

			fMnemonics = new Mnemonic[fBitField.fMnemonics.size()];
			Mnemonic mnemonicValue = null;
			int i = 0;
			for (Map.Entry<String, String> mnemonicEntry : fBitField.fMnemonics.entrySet()) {
				fMnemonics[i] = new Mnemonic(mnemonicEntry.getKey(), mnemonicEntry.getValue(), fBitField.fCount);
				if (fValue.equals(fMnemonics[i].fValue)) {
					mnemonicValue = fMnemonics[i];
				}
				i++;
			}
			fMnemonicValue = mnemonicValue;
		}

		@Override
		public IBitGroup[] getBitGroup() {
			return fBitGroups;
		}

		@Override
		public IMnemonic[] getMnemonics() {
			return fMnemonics;
		}

		@Override
		public boolean isZeroBasedNumbering() {
			return true;
		}

		@Override
		public boolean isZeroBitLeftMost() {
			return true;
		}

		@Override
		public boolean isReadable() {
			return true;
		}

		@Override
		public boolean isReadOnce() {
			return false;
		}

		@Override
		public boolean isWriteable() {
			return true;
		}

		@Override
		public boolean isWriteOnce() {
			return false;
		}

		@Override
		public boolean hasSideEffects() {
			return false;
		}

		public boolean isFloat() {
			return false;
		}

		@Override
		public String getName() {
			return fBitField.fName;
		}

		@Override
		public String getDescription() {
			return "Description of the " + fBitField.fName + " bit field";
		}

		@Override
		public IMnemonic getCurrentMnemonicValue() {
			return fMnemonicValue;
		}
	}

	private static class RegisterChangedDMEvent extends AbstractDMEvent<IRegisterDMContext>
			implements IRegisterChangedDMEvent {
		RegisterChangedDMEvent(IRegisterDMContext registerDmc) {
			super(registerDmc);
		}
	}

	private PDACommandControl fCommandControl;
	private PDAExpressions fExpressions;
	private CommandCache fNamesCache;

	public PDARegisters(DsfSession session) {
		super(session);
	}

	@Override
	protected BundleContext getBundleContext() {
		return PDAPlugin.getBundleContext();
	}

	@Override
	public void initialize(final RequestMonitor requestMonitor) {
		super.initialize(new RequestMonitor(getExecutor(), requestMonitor) {
			@Override
			protected void handleSuccess() {
				doInitialize(requestMonitor);
			}
		});
	}

	private void doInitialize(RequestMonitor requestMonitor) {

		fCommandControl = getServicesTracker().getService(PDACommandControl.class);
		fExpressions = getServicesTracker().getService(PDAExpressions.class);

		// Create the cache to store the register definitions.  This cache
		// only needs to be reset upon the "registers" event and is available
		// all the time.
		fNamesCache = new CommandCache(getSession(), fCommandControl);
		fNamesCache.setContextAvailable(fCommandControl.getContext(), true);

		// Add the register service as a listener to PDA events, to catch
		// the "registers" events from the command control.
		fCommandControl.addEventListener(this);

		// Sign up so we see events. We use these events to decide how to manage
		// any local caches we are providing as well as the lower level register
		// cache we create to get/set registers on the target.
		getSession().addServiceEventListener(this, null);

		// Make ourselves known so clients can use us.
		register(new String[] { IRegisters.class.getName(), PDARegisters.class.getName() },
				new Hashtable<String, String>());

		requestMonitor.done();
	}

	@Override
	public void shutdown(RequestMonitor requestMonitor) {
		unregister();
		fCommandControl.removeEventListener(this);
		getSession().removeServiceEventListener(this);
		super.shutdown(requestMonitor);
	}

	@Override
	public void getRegisterGroups(IDMContext ctx, final DataRequestMonitor<IRegisterGroupDMContext[]> rm) {
		final PDAVirtualMachineDMContext dmc = DMContexts.getAncestorOfType(ctx, PDAVirtualMachineDMContext.class);
		if (dmc == null) {
			PDAPlugin.failRequest(rm, INVALID_HANDLE, "Container context not found"); //$NON-NLS-1$
			return;
		}

		fNamesCache.execute(new PDAGroupsCommand(dmc), new DataRequestMonitor<PDAListResult>(getExecutor(), rm) {
			@Override
			protected void handleSuccess() {
				IRegisterGroupDMContext[] groups = new IRegisterGroupDMContext[getData().fValues.length];
				for (int i = 0; i < getData().fValues.length; i++) {
					groups[i] = new RegisterGroupDMContext(getSession().getId(), dmc, getData().fValues[i]);
				}
				rm.setData(groups);
				rm.done();
			}
		});
	}

	@Override
	public void getRegisters(final IDMContext ctx, final DataRequestMonitor<IRegisterDMContext[]> rm) {
		final PDAThreadDMContext execDmc = DMContexts.getAncestorOfType(ctx, PDAThreadDMContext.class);
		if (execDmc == null) {
			PDAPlugin.failRequest(rm, INVALID_HANDLE, "Thread context not found"); //$NON-NLS-1$
			return;
		}

		final RegisterGroupDMContext groupDmc = DMContexts.getAncestorOfType(ctx, RegisterGroupDMContext.class);
		if (groupDmc == null) {
			PDAPlugin.failRequest(rm, INVALID_HANDLE, "Group context not found"); //$NON-NLS-1$
			return;
		}

		fNamesCache.execute(new PDARegistersCommand(execDmc, groupDmc != null ? groupDmc.fName : null),
				new DataRequestMonitor<PDARegistersCommandResult>(getExecutor(), rm) {
					@Override
					protected void handleSuccess() {
						IRegisterDMContext[] groups = new IRegisterDMContext[getData().fRegisters.length];
						for (int i = 0; i < getData().fRegisters.length; i++) {
							groups[i] = new RegisterDMContext(getSession().getId(), execDmc, groupDmc,
									getData().fRegisters[i]);
						}
						rm.setData(groups);
						rm.done();
					}
				});

	}

	@Override
	public void getBitFields(IDMContext dmc, DataRequestMonitor<IBitFieldDMContext[]> rm) {

		RegisterDMContext registerDmc = DMContexts.getAncestorOfType(dmc, RegisterDMContext.class);

		if (registerDmc == null) {
			PDAPlugin.failRequest(rm, INVALID_HANDLE, "No register in context: " + dmc); //$NON-NLS-1$
			return;
		}

		PDABitField[] bitFields = registerDmc.fRegister.fBitFields;
		BitFieldDMContext[] bitFieldDMCs = new BitFieldDMContext[bitFields.length];

		for (int i = 0; i < bitFields.length; i++) {
			bitFieldDMCs[i] = new BitFieldDMContext(getSession().getId(), registerDmc, bitFields[i]);
		}

		rm.setData(bitFieldDMCs);
		rm.done();
	}

	@Override
	public void writeRegister(final IRegisterDMContext regCtx, String regValue, String formatId,
			final RequestMonitor rm) {
		if (regCtx instanceof RegisterDMContext) {
			IExpressionDMContext exprCtx = createRegisterExpressionDmc((RegisterDMContext) regCtx);
			fExpressions.writeExpression(exprCtx, regValue, formatId, false, new RequestMonitor(getExecutor(), rm) {
				@Override
				protected void handleSuccess() {
					generateRegisterChangedEvent((RegisterDMContext) regCtx);
					rm.done();
				}
			});

		} else {
			PDAPlugin.failRequest(rm, INVALID_HANDLE, "Invalid context"); //$NON-NLS-1$
		}

	}

	@Override
	public void writeBitField(final IBitFieldDMContext bitFieldCtx, String bitFieldValue, String formatId,
			final RequestMonitor rm) {
		if (bitFieldCtx instanceof BitFieldDMContext) {
			IExpressionDMContext exprCtx = createBitFieldExpressionDmc((BitFieldDMContext) bitFieldCtx);
			fExpressions.writeExpression(exprCtx, bitFieldValue, formatId, false,
					new RequestMonitor(getExecutor(), rm) {
						@Override
						protected void handleSuccess() {
							generateRegisterChangedEvent(
									DMContexts.getAncestorOfType(bitFieldCtx, RegisterDMContext.class));
							rm.done();
						}
					});
		} else {
			PDAPlugin.failRequest(rm, INVALID_HANDLE, "Invalid context"); //$NON-NLS-1$
		}
	}

	@Override
	public void writeBitField(IBitFieldDMContext bitFieldCtx, IMnemonic mnemonic, RequestMonitor rm) {
		if (mnemonic instanceof Mnemonic) {
			writeBitField(bitFieldCtx, ((Mnemonic) mnemonic).fValue.toString(), NATURAL_FORMAT, rm);
		} else {
			PDAPlugin.failRequest(rm, INVALID_HANDLE, "Invalid mnemonic"); //$NON-NLS-1$
		}
	}

	@Override
	public void getAvailableFormats(IFormattedDataDMContext dmc, DataRequestMonitor<String[]> rm) {
		IExpressionDMContext exprCtx = null;
		if (dmc instanceof RegisterDMContext) {
			exprCtx = createRegisterExpressionDmc((RegisterDMContext) dmc);
		} else if (dmc instanceof BitFieldDMContext) {
			exprCtx = createBitFieldExpressionDmc((BitFieldDMContext) dmc);
		}
		if (exprCtx != null) {
			fExpressions.getAvailableFormats(exprCtx, rm);
		} else {
			throw new IllegalArgumentException("Invalid register/bit field context " + dmc);
		}
	}

	@Override
	public FormattedValueDMContext getFormattedValueContext(IFormattedDataDMContext dmc, String formatId) {
		IExpressionDMContext exprCtx = null;
		if (dmc instanceof RegisterDMContext) {
			exprCtx = createRegisterExpressionDmc((RegisterDMContext) dmc);
		} else if (dmc instanceof BitFieldDMContext) {
			exprCtx = createBitFieldExpressionDmc((BitFieldDMContext) dmc);
		}
		if (exprCtx != null) {
			return fExpressions.getFormattedValueContext(exprCtx, formatId);
		} else {
			throw new IllegalArgumentException("Invalid register/bit field context " + dmc);
		}
	}

	@Override
	public void findRegisterGroup(IDMContext ctx, String name, DataRequestMonitor<IRegisterGroupDMContext> rm) {
		PDAPlugin.failRequest(rm, NOT_SUPPORTED, "Finding context not supported"); //$NON-NLS-1$
	}

	@Override
	public void findRegister(IDMContext ctx, String name, DataRequestMonitor<IRegisterDMContext> rm) {
		PDAPlugin.failRequest(rm, NOT_SUPPORTED, "Finding context not supported"); //$NON-NLS-1$
	}

	@Override
	public void findBitField(IDMContext ctx, String name, DataRequestMonitor<IBitFieldDMContext> rm) {
		PDAPlugin.failRequest(rm, NOT_SUPPORTED, "Finding context not supported"); //$NON-NLS-1$
	}

	@Override
	public void getFormattedExpressionValue(FormattedValueDMContext dmc, DataRequestMonitor<FormattedValueDMData> rm) {
		fExpressions.getFormattedExpressionValue(dmc, rm);
	}

	@Override
	public void getRegisterGroupData(IRegisterGroupDMContext regGroupDmc, DataRequestMonitor<IRegisterGroupDMData> rm) {
		if (regGroupDmc instanceof RegisterGroupDMContext) {
			rm.setData(new RegisterGroupDMData(((RegisterGroupDMContext) regGroupDmc).fName));
			rm.done();
		} else {
			PDAPlugin.failRequest(rm, INVALID_HANDLE, "Invalid context"); //$NON-NLS-1$
		}
	}

	@Override
	public void getRegisterData(IRegisterDMContext regDmc, DataRequestMonitor<IRegisterDMData> rm) {
		if (regDmc instanceof RegisterDMContext) {
			rm.setData(new RegisterDMData(((RegisterDMContext) regDmc).fRegister));
			rm.done();
		} else {
			PDAPlugin.failRequest(rm, INVALID_HANDLE, "Invalid context"); //$NON-NLS-1$
		}
	}

	@Override
	public void getBitFieldData(IBitFieldDMContext dmc, final DataRequestMonitor<IBitFieldDMData> rm) {
		if (!(dmc instanceof BitFieldDMContext)) {
			PDAPlugin.failRequest(rm, INVALID_HANDLE, "Invalid context"); //$NON-NLS-1$
		}
		final BitFieldDMContext bitFieldDmc = (BitFieldDMContext) dmc;

		IExpressionDMContext bitFieldExprDmc = createBitFieldExpressionDmc(bitFieldDmc);
		FormattedValueDMContext formattedBitFieldDmc = fExpressions.getFormattedValueContext(bitFieldExprDmc,
				NATURAL_FORMAT);
		fExpressions.getFormattedExpressionValue(formattedBitFieldDmc,
				new DataRequestMonitor<FormattedValueDMData>(getExecutor(), rm) {
					@Override
					protected void handleSuccess() {
						rm.setData(new BitFieldDMData(bitFieldDmc.fBitField, getData().getFormattedValue()));
						rm.done();
					}
				});
	}

	private IExpressionDMContext createRegisterExpressionDmc(RegisterDMContext dmc) {
		return fExpressions.createExpression(dmc, "$" + dmc.fRegister.fName);
	}

	private IExpressionDMContext createBitFieldExpressionDmc(BitFieldDMContext dmc) {
		RegisterDMContext regDmc = DMContexts.getAncestorOfType(dmc, RegisterDMContext.class);
		return fExpressions.createExpression(dmc, "$" + regDmc.fRegister.fName + "." + dmc.fBitField.fName);
	}

	@DsfServiceEventHandler
	public void eventDispatched(IRunControl.IResumedDMEvent e) {
	}

	@DsfServiceEventHandler
	public void eventDispatched(IRunControl.ISuspendedDMEvent e) {
	}

	@DsfServiceEventHandler
	public void eventDispatched(final IRegisters.IRegisterChangedDMEvent e) {
	}

	private void generateRegisterChangedEvent(RegisterDMContext dmc) {
		getSession().dispatchEvent(new RegisterChangedDMEvent(dmc), getProperties());
	}

	@Override
	public void eventReceived(Object output) {
		if (!(output instanceof String))
			return;
		if ("registers".equals(output)) {
			fNamesCache.reset();
		}
	}

	@Override
	public void flushCache(IDMContext context) {
		fExpressions.flushCache(context);
	}
}
