context: /*
      Copyright (C) 2014-2019 de4dot@gmail.com
  
      This file is part of dnSpy
  
      dnSpy is free software: you can redistribute it and/or modify
      it under the terms of the GNU General Public License as published by
      the Free Software Foundation, either version 3 of the License, or
      (at your option) any later version.
  
      dnSpy is distributed in the hope that it will be useful,
      but WITHOUT ANY WARRANTY; without even the implied warranty of
      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      GNU General Public License for more details.
  
      You should have received a copy of the GNU General Public License
      along with dnSpy.  If not, see <http://www.gnu.org/licenses/>.
  */
  
  using System;
  using System.Windows.Input;
  using dnlib.DotNet;
  using dnlib.DotNet.Resources;
  using dnSpy.AsmEditor.Properties;
  using dnSpy.AsmEditor.ViewHelpers;
  using dnSpy.Contracts.MVVM;
  
  namespace dnSpy.AsmEditor.Resources {
  	enum ResourceElementType {
  		Null			= ResourceTypeCode.Null,
  		String			= ResourceTypeCode.String,
  		Boolean			= ResourceTypeCode.Boolean,
  		Char			= ResourceTypeCode.Char,
  		Byte			= ResourceTypeCode.Byte,
  		SByte			= ResourceTypeCode.SByte,
  		Int16			= ResourceTypeCode.Int16,
  		UInt16			= ResourceTypeCode.UInt16,
  		Int32			= ResourceTypeCode.Int32,
  		UInt32			= ResourceTypeCode.UInt32,
  		Int64			= ResourceTypeCode.Int64,
  		UInt64			= ResourceTypeCode.UInt64,
  		Single			= ResourceTypeCode.Single,
  		Double			= ResourceTypeCode.Double,
  		Decimal			= ResourceTypeCode.Decimal,
  		DateTime		= ResourceTypeCode.DateTime,
  		TimeSpan		= ResourceTypeCode.TimeSpan,
  		ByteArray		= ResourceTypeCode.ByteArray,
  		Stream			= ResourceTypeCode.Stream,
  		SerializedType	= ResourceTypeCode.UserTypes,
  	}
  
  	sealed class ResourceElementVM : ViewModelBase {
  		readonly ResourceElementOptions origOptions;
  
  		public IOpenFile OpenFile {
  			set => openFile = value;
  		}
  		IOpenFile? openFile;
  
  		public IDnlibTypePicker DnlibTypePicker {
  			set => UserTypeVM.DnlibTypePicker = value;
  		}
  
  		public ICommand ReinitializeCommand => new RelayCommand(a => Reinitialize());
  		public ICommand PickRawBytesCommand => new RelayCommand(a => PickRawBytes(), a => IsRawBytes);
  
  		public bool CanChangeType {
  			get => canChangeType;
  			set {
  				if (canChangeType != value) {
  					canChangeType = value;
  					OnPropertyChanged(nameof(CanChangeType));
  				}
  			}
  		}
  		bool canChangeType = true;
  
  		internal static readonly EnumVM[] resourceElementTypeList = EnumVM.Create(false, typeof(ResourceElementType));
  		public EnumListVM ResourceElementTypeVM { get; }
  
  		public string? Name {
  			get => name;
  			set {
  				if (name != value) {
  					name = value;
  					OnPropertyChanged(nameof(Name));
  				}
  			}
  		}
  		UTF8String? name;
  
  		public string? String {
  			get => @string;
  			set {
  				if (@string != value) {
  					@string = value;
  					OnPropertyChanged(nameof(String));
  				}
  			}
  		}
  		string? @string;
  
  		public BooleanVM BooleanVM { get; }
  		public CharVM CharVM { get; }
  		public ByteVM ByteVM { get; }
  		public SByteVM SByteVM { get; }
  		public Int16VM Int16VM { get; }
  		public UInt16VM UInt16VM { get; }
  		public Int32VM Int32VM { get; }
  		public UInt32VM UInt32VM { get; }
  		public Int64VM Int64VM { get; }
  		public UInt64VM UInt64VM { get; }
  		public SingleVM SingleVM { get; }
  		public DoubleVM DoubleVM { get; }
  		public DecimalVM DecimalVM { get; }
  		public DateTimeVM DateTimeVM { get; }
  		public TimeSpanVM TimeSpanVM { get; }
  
  		public byte[]? Data {
  			get => data;
  			set {
  				if (data != value) {
  					data = value;
  					OnPropertyChanged(nameof(Data));
  					OnPropertyChanged(nameof(DataString));
  				}
  			}
  		}
  		byte[]? data;
  
  		public string DataString => string.Format(dnSpy_AsmEditor_Resources.XBytes, Data is null ? 0 : Data.Length);
  		public UserTypeVM UserTypeVM { get; }
  
  		public object? ValueVM {
  			get {
  				switch ((ResourceElementType)ResourceElementTypeVM.SelectedItem!) {
  				case ResourceElementType.Null:		return null;
  				case ResourceElementType.String:	return null;
  				case ResourceElementType.Boolean:	return BooleanVM;
  				case ResourceElementType.Char:		return CharVM;
  				case ResourceElementType.Byte:		return ByteVM;
  				case ResourceElementType.SByte:		return SByteVM;
  				case ResourceElementType.Int16:		return Int16VM;
  				case ResourceElementType.UInt16:	return UInt16VM;
  				case ResourceElementType.Int32:		return Int32VM;
  				case ResourceElementType.UInt32:	return UInt32VM;
  				case ResourceElementType.Int64:		return Int64VM;
  				case ResourceElementType.UInt64:	return UInt64VM;
  				case ResourceElementType.Single:	return SingleVM;
  				case ResourceElementType.Double:	return DoubleVM;
  				case ResourceElementType.Decimal:	return DecimalVM;
  				case ResourceElementType.DateTime:	return DateTimeVM;
  				case ResourceElementType.TimeSpan:	return TimeSpanVM;
  				case ResourceElementType.ByteArray:	return null;
  				case ResourceElementType.Stream:	return null;
  				case ResourceElementType.SerializedType: return UserTypeVM;
  				default: throw new InvalidOperationException();
  				}
  			}
  		}
  
  		public bool IsSerializedType => (ResourceElementType)ResourceElementTypeVM.SelectedItem! == ResourceElementType.SerializedType;
  		public bool IsSingleLineValue => !IsMultiLineValue && !IsRawBytes && (ResourceElementType)ResourceElementTypeVM.SelectedItem! != ResourceElementType.Null;
  		public bool IsMultiLineValue => (ResourceElementType)ResourceElementTypeVM.SelectedItem! == ResourceElementType.String;
  
  		public bool IsRawBytes {
  			get {
  				var code = (ResourceElementType)ResourceElementTypeVM.SelectedItem!;
  				return code == ResourceElementType.ByteArray || code == ResourceElementType.Stream;
  			}
  		}
  
  		readonly bool canDeserialize;
  
  		public ResourceElementVM(ResourceElementOptions options, ModuleDef ownerModule, bool canDeserialize) {
  			origOptions = options;
  			this.canDeserialize = canDeserialize;
  
  			BooleanVM = new BooleanVM(a => HasErrorUpdated());
  			CharVM = new CharVM(a => HasErrorUpdated());
  			ByteVM = new ByteVM(a => HasErrorUpdated());
  			SByteVM = new SByteVM(a => HasErrorUpdated());
  			Int16VM = new Int16VM(a => HasErrorUpdated());
  			UInt16VM = new UInt16VM(a => HasErrorUpdated());
  			Int32VM = new Int32VM(a => HasErrorUpdated());
  			UInt32VM = new UInt32VM(a => HasErrorUpdated());
  			Int64VM = new Int64VM(a => HasErrorUpdated());
  			UInt64VM = new UInt64VM(a => HasErrorUpdated());
  			SingleVM = new SingleVM(a => HasErrorUpdated());
  			DoubleVM = new DoubleVM(a => HasErrorUpdated());
  			DecimalVM = new DecimalVM(a => HasErrorUpdated());
  			DateTimeVM = new DateTimeVM(a => HasErrorUpdated());
  			TimeSpanVM = new TimeSpanVM(a => HasErrorUpdated());
  			UserTypeVM = new UserTypeVM(ownerModule, canDeserialize);
  			ResourceElementTypeVM = new EnumListVM(resourceElementTypeList, (a, b) => OnResourceElementTypeChanged());
  
  			UserTypeVM.PropertyChanged += (s, e) => {
  				if (e.PropertyName == nameof(UserTypeVM.HasError))
  					HasErrorUpdated();
  			};
  
  			Reinitialize();
  		}
  
  		void PickRawBytes() {
  			if (openFile is null)
  				throw new InvalidOperationException();
  			var newBytes = openFile.Open();
  			if (!(newBytes is null))
  				Data = newBytes;
  		}
  
  		void OnResourceElementTypeChanged() {
  			OnPropertyChanged(nameof(ValueVM));
  			OnPropertyChanged(nameof(IsSerializedType));
  			OnPropertyChanged(nameof(IsSingleLineValue));
  			OnPropertyChanged(nameof(IsMultiLineValue));
  			OnPropertyChanged(nameof(IsRawBytes));
  			HasErrorUpdated();
  		}
  
  		void Reinitialize() => InitializeFrom(origOptions);
  		public ResourceElementOptions CreateResourceElementOptions() => CopyTo(new ResourceElementOptions());
  
  		void InitializeFrom(ResourceElementOptions options) {
  			Name = options.Name;
  			var code = Convert(options.ResourceData!.Code);
  			var builtin = options.ResourceData as BuiltInResourceData;
  			ResourceElementTypeVM.SelectedItem = code;
  			switch (code) {
  			case ResourceElementType.Null:		break;
  			case ResourceElementType.String:	String = (string)builtin!.Data; break;
  			case ResourceElementType.Boolean:	BooleanVM.Value = (bool)builtin!.Data; break;
  			case ResourceElementType.Char:		CharVM.Value = (char)builtin!.Data; break;
  			case ResourceElementType.Byte:		ByteVM.Value = (byte)builtin!.Data; break;
  			case ResourceElementType.SByte:		SByteVM.Value = (sbyte)builtin!.Data; break;
  			case ResourceElementType.Int16:		Int16VM.Value = (short)builtin!.Data; break;
  			case ResourceElementType.UInt16:	UInt16VM.Value = (ushort)builtin!.Data; break;
  			case ResourceElementType.Int32:		Int32VM.Value = (int)builtin!.Data; break;
  			case ResourceElementType.UInt32:	UInt32VM.Value = (uint)builtin!.Data; break;
  			case ResourceElementType.Int64:		Int64VM.Value = (long)builtin!.Data; break;
  			case ResourceElementType.UInt64:	UInt64VM.Value = (ulong)builtin!.Data; break;
  			case ResourceElementType.Single:	SingleVM.Value = (float)builtin!.Data; break;
  			case ResourceElementType.Double:	DoubleVM.Value = (double)builtin!.Data; break;
  			case ResourceElementType.Decimal:	DecimalVM.Value = (decimal)builtin!.Data; break;
  			case ResourceElementType.DateTime:	DateTimeVM.Value = (DateTime)builtin!.Data; break;
  			case ResourceElementType.TimeSpan:	TimeSpanVM.Value = (TimeSpan)builtin!.Data; break;
  			case ResourceElementType.ByteArray:	Data = (byte[])builtin!.Data; break;
  			case ResourceElementType.Stream:	Data = (byte[])builtin!.Data; break;
  
  			case ResourceElementType.SerializedType:
  				var binRes = (BinaryResourceData)options.ResourceData;
  				UserTypeVM.TypeFullName = binRes.TypeName;
  				UserTypeVM.SetData(binRes.Data);
  				break;
  
  
gt: 			default: throw new InvalidOperationException();