using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows.Forms;

namespace CodeGenerator
{
	public partial class CodeGeneratorForm : Form
	{
		private Dictionary<string, ProjectSetting> ProjectDict = new Dictionary<string, ProjectSetting>();

		public CodeGeneratorForm()
		{
			InitializeComponent();
			this.Load += CodeGeneratorForm_Load;
		}

		void CodeGeneratorForm_Load(object sender, EventArgs e)
		{
			//chkYbs.Checked = ConfigurationManager.AppSettings["Ybs"] == "true";

			this.Shown += CodeGeneratorForm_Shown;
			this.KeyDown += CodeGeneratorForm_KeyDown;
			txtFind.TextChanged += txtFind_TextChanged;
			treeView1.DoubleClick += treeView1_DoubleClick;
			btnRefreshClass.Click += btnRefreshClass_Click;
			btnAppendWhere.Click += btnAppendWhere_Click;
			btnGenerate.Click += btnGenerate_Click;
			btnParseTableName.Click += btnParseTableName_Click;
			cmbProject.SelectedIndexChanged += CmbProject_SelectedIndexChanged;
			btnNewProject.Click += BtnNewProject_Click;
			btnRefreshProject.Click += BtnRefreshProject_Click;
		}

		void CodeGeneratorForm_Shown(object sender, EventArgs e)
		{
			LoadProjectSetting();
		}

		private void LoadProjectSetting()
		{
			ProjectDict.Clear();
			cmbProject.Items.Clear();

			List<ProjectSetting> list = ProjectSetting.Load();
			foreach (ProjectSetting project in list)
			{
				cmbProject.Items.Add(project.ProjectName);
				ProjectDict.Add(project.ProjectName, project);
			}
		}

		private string Namespace
		{
			get
			{
				//return ConfigurationManager.AppSettings["namespace"];
				return CurrentProject.NameSpace;
			}
		}

		private void Generate(bool isWriteFile)
		{
			string code = @"namespace {5}
{{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.ComponentModel.DataAnnotations;
    using System.ComponentModel.DataAnnotations.Schema;
    using System.Data.Entity.Spatial;

	{5}
	[Table(""{0}""), DisplayName(""{1}"")]
    public partial class {0}
    {{
		public const string SQL = @""
{2}"";

{3}
{4}	
	}}
}}	
";
			if (chkAutoAppendWhere.Checked)
			{
				if (!txtSql.Text.Contains("{0}"))
				{
					btnAppendWhere.PerformClick();
				}
			}

			string sql = ReplaceMarco();

			txtMainTable.Text = GetMainTableName(sql);
			txtRelationTableNames.Lines = GetRelationTableNames(sql).ToArray();

			List<TableFieldInfo> tableFieldList = GetFieldList(txtMainTable.Text.Trim());
			foreach (string relationTable in txtRelationTableNames.Lines)
			{
				foreach (TableFieldInfo fi in GetFieldList(relationTable))
				{
					if (!tableFieldList.Exists(a => a.Name == fi.Name))
					{
						tableFieldList.Add(fi);
					}
				}
			}

			string properties = GetProperties(tableFieldList);
			string constFields = GenerateConstField(tableFieldList);

			if (string.IsNullOrEmpty(Namespace))
			{
				throw new Exception("Namespace is null");
			}

			string tableDisplayName = GetTableDisplayName(txtMainTable.Text);

			string sqlstring = txtSql.Text.Trim().Replace("--{0}", "{0}");
			string serializableAttribute = CurrentProject.HasSerializable ? "[Serializable]" : "";
			code = string.Format(code, txtMainTable.Text, tableDisplayName, sqlstring, properties, constFields, Namespace, serializableAttribute);
			//Console.WriteLine(code);

			if (isWriteFile)
			{
				string path = GetPath(false);
				if (path == null)
				{
					return;
				}
				File.WriteAllText(path, code, Encoding.Unicode);

				WriteExtClass();
				MessageBox.Show("生成完毕");
			}
		}

		private void WriteExtClass()
		{
			string path = GetPath(true);
			if (File.Exists(path))
			{
				return;
			}

			string text = @"using System;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations.Schema;

namespace {1}
{{
    public partial class {0}
    {{
	}}
}}";
			text = string.Format(text, txtMainTable.Text.Trim(), Namespace);
			File.WriteAllText(path, text);
		}

		private string GetMainTableName(string sql)
		{
			if (checkBox1.Checked)
			{
				return txtMainTable.Text;
			}

			string s = sql;
			List<string> keys = s.Split(' ', '\r', '\n', '\t').Where(a => a != "").ToList();
			for (int i = 0; i < keys.Count; i++)
			{
				string key = keys[i];
				if (key.Trim().ToLower() == "from") //第一个from后面就是主表。
				{
					return keys[i + 1];
				}
			}
			return "";
		}

		private string GetTableDisplayName(string tableName)
		{
			string sql = "";
			if (!chkYbs.Checked)
			{
				sql = @"select g.[Value] TableDesc
from sys.tables a
left join sys.extended_properties g on a.object_id = g.major_id AND g.minor_id = 0
where a.name = '{0}'";
			}
			else
			{
				sql = "select DispLabel TableDesc from sysFieldProperty where TableName='{0}' and isnull(FieldName, '') = ''";
			}

			sql = string.Format(sql, tableName);
			using (SqlConnection conn = new SqlConnection())
			{
				//conn.ConnectionString = ConfigurationManager.ConnectionStrings["ConnectionString"].ConnectionString;
				conn.ConnectionString = CurrentProject.ConnectionString;
				conn.Open();
				SqlCommand cmd = conn.CreateCommand();
				cmd.CommandText = string.Format(sql, tableName);
				SqlDataReader reader = cmd.ExecuteReader();
				if (reader.Read())
				{
					return reader["TableDesc"].ToString();
				}
				return "";
			}
		}

		private List<string> GetRelationTableNames(string sql)
		{
			List<string> tableNames = new List<string>();

			string s = sql.ToLower(); //全部转成小写，方便分析。
			List<string> keys = s.Split(' ', '\r', '\n', '\t', '(', ')').Where(a => a != "").ToList();

			bool isFromBegin = false;
			bool isSubQuery = false;
			for (int i = 0; i < keys.Count; i++)
			{
				string key = keys[i];
				if (key == "from")
				{
					isFromBegin = true;
				}

				if (!isFromBegin) //只从from语句开始分析。
				{
					continue;
				}

				if (isSubQuery)
				{
					if (key == ")")
					{
						tableNames.Add(keys[i + 1]);
					}
				}
				else
				{
					if (key.Trim().ToLower() == "join") //join后面的是关系表。
					{
						string s2 = keys[i + 1];
						if (s2 != "(")
						{
							tableNames.Add(s2);
						}
						else //如果join后面是子查询；
						{
							isSubQuery = true;
						}
					}
				}
			}

			return tableNames;
		}

		private List<TableFieldInfo> GetFieldList(string tableName)
		{
			List<TableFieldInfo> list = new List<TableFieldInfo>();
			using (SqlConnection conn = new SqlConnection())
			{
				//conn.ConnectionString = ConfigurationManager.ConnectionStrings["ConnectionString"].ConnectionString;
				conn.ConnectionString = CurrentProject.ConnectionString;
				conn.Open();
				SqlCommand cmd = conn.CreateCommand();
				cmd.CommandText = string.Format(TableFieldInfo.SQL, tableName);
				SqlDataReader reader = cmd.ExecuteReader();
				while (reader.Read())
				{
					TableFieldInfo tableField = new TableFieldInfo()
					{
						TableName = tableName,
						ColumnId = (int)reader["ColumnId"],
						Name = (string)reader["Name"],
						PrimaryKey = (int)reader["PrimaryKey"] == 1,
						Nullable = (bool)reader["Nullable"],
						ColumnDesc = (string)reader["ColumnDesc"]
					};
					list.Add(tableField);
				}
				reader.Close();
			}

			if (chkYbs.Checked)
			{
				using (SqlConnection conn = new SqlConnection())
				{
					//conn.ConnectionString = ConfigurationManager.ConnectionStrings["ConnectionString"].ConnectionString;
					conn.ConnectionString = CurrentProject.ConnectionString;
					conn.Open();
					SqlCommand cmd = conn.CreateCommand();
					cmd.CommandText = string.Format("select * from sysFieldProperty where TableName='{0}'", tableName);
					SqlDataReader reader = cmd.ExecuteReader();
					while (reader.Read())
					{
						TableFieldInfo fieldInfo = list.FirstOrDefault(a => a.Name == (string)reader["FieldName"]);
						if (fieldInfo != null)
						{
							fieldInfo.ColumnDesc = (string)reader["DispLabel"];
						}
					}
					reader.Close();
				}
			}

			return list;
		}

		private string ReplaceMarco()
		{
			string sql = txtSql.Text;
			List<string> macroList = ParseMacro();
			foreach (string macro in macroList)
			{
				sql = sql.Replace(macro, txtWhere.Text);
			}
			return sql;
		}

		private string GetProperties(List<TableFieldInfo> tableFieldList)
		{
			string properties = "";

			string sql = ReplaceMarco();

			using (SqlConnection conn = new SqlConnection())
			{
				//conn.ConnectionString = ConfigurationManager.ConnectionStrings["connectionString"].ConnectionString;
				conn.ConnectionString = CurrentProject.ConnectionString;
				conn.Open();
				SqlCommand cmd = conn.CreateCommand();
				cmd.CommandText = sql;
				SqlDataReader reader = cmd.ExecuteReader(CommandBehavior.SchemaOnly);
				for (int i = 0; i < reader.FieldCount; i++)
				{
					Type fieldType = reader.GetFieldType(i);
					string fieldName = reader.GetName(i);
					bool nullable = false;
					TableFieldInfo tableFieldInfo = tableFieldList.FirstOrDefault(a => a.Name.ToLower() == fieldName.ToLower());

					if (tableFieldInfo != null) //如果此字段属于主表字段，
					{
						properties += string.Format("\t\t[DisplayName(\"{0}\")]\r\n", tableFieldInfo.ColumnDesc);

						if (tableFieldInfo.PrimaryKey && tableFieldInfo.TableName == txtMainTable.Text)
						{
							properties += string.Format("\t\t[Key, Column(Order = {0})]\r\n", tableFieldInfo.ColumnId);
						}
						else if (tableFieldInfo.Name.ToLower() == "version")
						{
							properties += "\t\t[Column(TypeName = \"timestamp\")][DatabaseGenerated(DatabaseGeneratedOption.Computed)][MaxLength(8)]\r\n";
						}

						if (fieldType != typeof(string) && fieldName.ToLower() != "version")
						{
							nullable = tableFieldInfo.Nullable;
						}
					}

					if (tableFieldInfo == null || tableFieldInfo.TableName != txtMainTable.Text)
					{
						properties += "\t\t[NotMapped]\r\n";
					}

					properties += string.Format("\t\tpublic {0}{1} {2} {{get;set;}}\r\n\r\n",
						fieldType.Name,
						nullable ? "?" : "",
						fieldName);
				}
			}
			return properties;
		}

		private string GenerateConstField(List<TableFieldInfo> tableFieldList)
		{
			string s = string.Format("\t\tpublic const string F_TableName = \"{0}\";\r\n", txtMainTable.Text);
			foreach (TableFieldInfo tf in tableFieldList)
			{
				s += string.Format("\t\tpublic const string F_{0} = \"{1}.{0}\";\r\n", tf.Name, tf.TableName);
			}
			return s;
		}

		private void btnGenerate_Click(object sender, EventArgs e)
		{
			Generate(true);
			checkBox1.Checked = false;
		}

		private static readonly Regex REG_MACRO = new Regex(@"\{[^\{\}]+\}");

		//解析sql语句中包含的宏。
		private List<string> ParseMacro()
		{
			List<string> lst = new List<string>();

			Match m = REG_MACRO.Match(txtSql.Text.Trim());
			while (m.Success)
			{
				string s = m.Value;
				if (!lst.Contains(s))
				{
					lst.Add(m.Value);
				}

				m = m.NextMatch();
			}

			return lst;
		}

		private string GetPath(bool isExt)
		{
			//string path = ConfigurationManager.AppSettings["CodePath"];
			string path = CurrentProject.CodePath;
			string file = "";
			if (isExt)
			{
				file = txtMainTable.Text + ".ext.cs";
			}
			else
			{
				file = txtMainTable.Text + ".cs";
			}

			string[] foundFiles = Directory.GetFiles(path, file, SearchOption.AllDirectories);
			if (foundFiles.Length == 0)
			{
				path = Path.Combine(path, file);
			}
			else if (foundFiles.Length == 1)
			{
				path = foundFiles[0];
			}
			else
			{
				MessageBox.Show(string.Format("发现多个同名文件：{0}", file));
				return null;
			}

			return path;
		}

		private void btnAppendWhere_Click(object sender, EventArgs e)
		{
			if (txtSql.Text.Contains("order by"))
			{
				int p = txtSql.Text.IndexOf("order by");
				string s1 = txtSql.Text.Substring(0, p);
				string s2 = txtSql.Text.Substring(p);
				txtSql.Text = s1 + "where 1=1 {0}\r\n" + s2;
			}
			else
			{
				txtSql.Text += "\r\nwhere 1=1 {0}";
			}
		}

		private void ScanClass()
		{
			if (CurrentProject == null)
			{
				return;
			}

			treeView1.BeginUpdate();
			treeView1.Nodes.Clear();

			string path = CurrentProject.DllPath;
			if (!File.Exists(path))
			{
				MessageBox.Show("Domain DLL文件不存在");
				return;
			}

			Assembly assembly = Assembly.Load(File.ReadAllBytes(path));
			if (assembly == null)
			{
				MessageBox.Show(string.Format("装载Domain DLL失败：{0}", path));
				return;
			}

			Type[] types = null;
			try
			{
				types = assembly.GetTypes();
			}
			catch (ReflectionTypeLoadException ex)
			{
				MessageBox.Show(string.Format("装载Domain DLL失败：{0}\r\n{1}", path, ex.LoaderExceptions[0].Message), "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
				return;
			}

			foreach (Type type in types)
			{
				FieldInfo fi = type.GetField("SQL");
				if (fi == null)
				{
					continue;
				}

				treeView1.Nodes.Add(type.FullName, type.Name);
				treeView1.Sort();
			}

			treeView1.EndUpdate();
		}

		private void btnRefreshClass_Click(object sender, EventArgs e)
		{
			ScanClass();
		}

		void treeView1_DoubleClick(object sender, EventArgs e)
		{
			ShowSQL();
		}

		void txtFind_TextChanged(object sender, EventArgs e)
		{
			if (txtFind.Text == "")
			{
				return;
			}

			foreach (TreeNode node in treeView1.Nodes)
			{
				if (node.Text.ToLower() == txtFind.Text.Trim().ToLower())
				{
					treeView1.SelectedNode = node;
					ShowSQL();
					break;
				}
			}
		}

		private void ShowSQL()
		{
			if (treeView1.SelectedNode == null)
			{
				return;
			}

			//string path = ConfigurationManager.AppSettings["DomainPath"];
			string path = CurrentProject.DllPath;
			Assembly assembly = Assembly.Load(File.ReadAllBytes(path));
			Type type = assembly.GetType(treeView1.SelectedNode.Name);
			FieldInfo fi = type.GetField("SQL");
			object obj = Activator.CreateInstance(type);
			string sql = (string)fi.GetValue(obj);
			txtSql.Text = sql.TrimStart('\r').TrimStart('\n').Trim();
		}

		void CodeGeneratorForm_KeyDown(object sender, KeyEventArgs e)
		{
			if (e.KeyCode == Keys.Escape)
			{
				txtFind.SelectAll();
				txtFind.Focus();
			}
		}

		private void btnParseTableName_Click(object sender, EventArgs e)
		{
			txtMainTable.Text = GetMainTableName(txtSql.Text);
			txtRelationTableNames.Lines = GetRelationTableNames(txtSql.Text).ToArray();
		}

		private void treeView1_AfterSelect(object sender, TreeViewEventArgs e)
		{
			treeView1.Tag = e.Node;
		}

		private ProjectSetting CurrentProject
		{
			get
			{
				if (ProjectDict.TryGetValue(cmbProject.Text, out ProjectSetting project))
				{
					return project;
				}
				return null;
			}
		}

		private void CmbProject_SelectedIndexChanged(object sender, EventArgs e)
		{
			if (CurrentProject == null)
			{
				return;
			}

			chkHasSerializable.Checked = CurrentProject.HasSerializable;

			ScanClass();
		}

		private void BtnNewProject_Click(object sender, EventArgs e)
		{
			ProjectSetting project = new ProjectSetting()
			{
				ProjectName = $"新建项目{ProjectDict.Count + 1}",
				ConnectionString = "data source =localhost; initial catalog = DbName; user id = sa; password = txy123; persist security info = True; MultipleActiveResultSets = True; App = EntityFramework",
				CodePath = " ",
				DllPath = " ",
				HasSerializable = false,
				NameSpace = " ",
				Ybs = false
			};

			List<ProjectSetting> projectList = ProjectDict.Values.ToList();
			projectList.Add(project);
			ProjectSetting.Save(projectList);

			MessageBox.Show("新建项目已经保存到配置文件，请修改配置文件，然后刷新项目列表");
		}

		private void BtnRefreshProject_Click(object sender, EventArgs e)
		{
			LoadProjectSetting();
		}

	}
}
