using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace Lost.JIT.AMD64
{
	[Serializable]
	public class Move: ProcessorInstruction
	{
		public Move(InstructionOperand dest, InstructionOperand source)
		{
			Dest = dest;
			Source = source;
		}

		public InstructionOperand Dest { get; set; }
		public InstructionOperand Source { get; set; }

		public override int? Size
		{
			get { throw new NotImplementedException(); }
		}

		public override string ToFASM()
		{
			if ((Dest is GeneralPurposeRegister || Dest is MemoryOperand)
				&& (Source is GeneralPurposeRegister || Source is MemoryOperand || Source is ImmediateOperand)
				) return string.Format("mov {0}, {1}", Dest, Source);
			throw new NotImplementedException();
		}

		public override string OpCodeFASM
		{
			get { return "mov"; }
		}

		public override void Compile(Stream destStream)
		{
			GeneralPurposeRegister reg;
			Rex rex = Rex.None;

			#region Source is Immediate
			if (Source is ImmediateOperand)
			{
				var source = Source as ImmediateOperand;
				if (Dest is GeneralPurposeRegister)
				{
					reg = Dest as GeneralPurposeRegister;
					int opcode = 0xB0;
					opcode += reg.Register.GetIndex();
					if (reg.Register.IsNew()) rex |= Rex.Reg;
					switch (reg.Size)
					{
					case 1:
						if (rex != Rex.None) destStream.WriteRex(rex);
						destStream.WriteByte(opcode);
						destStream.WriteByte(source.Value);
						return;
					case 2:
						destStream.WriteByte(OperandSizeOverride);
						if (rex != Rex.None) destStream.WriteRex(rex);
						destStream.WriteByte(opcode + 8);
						destStream.WriteShort(source.Value);
						return;
					case 4:
						if (rex != Rex.None) destStream.WriteRex(rex);
						destStream.WriteByte(opcode + 8);
						destStream.WriteInt(source.Value);
						return;
					case 8:
						rex |= Rex.Wide;
						if (rex != Rex.None) destStream.WriteRex(rex);
						destStream.WriteByte(opcode + 8);
						destStream.WriteLong(source.Value);
						return;
					default:
						throw new NotSupportedException();
					}
				}

				if (Dest is MemoryOperand)
				{
					var mem = Dest as MemoryOperand;
					throw new NotImplementedException();
				}
			}
			#endregion Source is Immediate

			int opcode_base = 0x88;
			#region MOV reg, reg
			if (Dest is GeneralPurposeRegister && Source is GeneralPurposeRegister)
			{
				var dest = Dest as GeneralPurposeRegister;
				var source = Source as GeneralPurposeRegister;
				if (dest.Size != source.Size) throw new NotSupportedException();
				if (dest.Register.IsNew()) rex |= Rex.NewRegRM;
				if (source.Register.IsNew()) rex |= Rex.Reg;
				switch (dest.Size)
				{
				case 1:
					break;
				case 2:
					destStream.WriteByte(OperandSizeOverride);
					goto case 4;
				case 4:
					opcode_base++;
					break;
				case 8:
					rex |= Rex.Wide;
					goto case 4;
				default:
					throw new NotSupportedException();
				}
				if (rex != Rex.None) destStream.WriteRex(rex);
				destStream.WriteByte(opcode_base);
				WriteOperands(source.Register, dest.Register, destStream);
				return;
			}
			#endregion MOV reg, reg

			#region MOV reg/mem, reg (MOV reg, reg/mem)
			var memory = Dest is GeneralPurposeRegister ? Source as MemoryOperand : Dest as MemoryOperand;
			reg = Dest is GeneralPurposeRegister ? Dest as GeneralPurposeRegister : Source as GeneralPurposeRegister;
			if (Dest is GeneralPurposeRegister) opcode_base += 2;
			rex = NeedsRex(memory);
			if (reg.Register.IsNew()) rex |= Rex.Reg;

			switch (reg.Size)
			{
			case 1:
				break;
			case 2:
				destStream.WriteByte(OperandSizeOverride);
				goto case 4;
			case 4:
				opcode_base++;
				break;
			case 8:
				rex |= Rex.Wide;
				goto case 4;
			default:
				throw new NotSupportedException();
			}

			if (rex != Rex.None) destStream.WriteRex(rex);
			destStream.WriteByte(opcode_base);
			WriteOperands(reg, memory, destStream);
			#endregion
		}
	}
}
