using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using System.IO;
using System.Windows.Forms;
using System.Drawing;
using System.Security.Cryptography;
using System.Security.Cryptography.Xml;

namespace EduNetworkBuilder
{
    public class PersonClass
    {
        public string filepath=""; //the path of the file.  We use the username as the file-name.
        public string StudentFilePath = "";//If admin, this is used to store the path of the directory where student files are stored.

        string _UserName="";
        /// <summary>
        /// The username of the person.  Will be used as a filename.  Cannot be changed once it is set
        /// </summary>
        public string UserName { get { return _UserName; } }
        /// <summary>
        /// The filename, without the path, of the file.  UserName + ".enbu"
        /// </summary>
        public string FileName { get { return UserName + ".enbu"; } }

        /// <summary>
        /// The full name of the person. Can have spaces and punctuation.  Can change as one wants it changed.
        /// </summary>
        public string FullName = "";  

        public string Password = "";

        string PasswordHint = "";
        /// <summary>
        /// The AltPassword is mainly used to hold the admin decrypting password.  The student account will
        /// use this to encrypt the student password, so the admin can open their file.
        /// </summary>
        public string AltPassword = "";
        /// <summary>
        /// Used to determine if the user we are working with is the admin account.
        /// </summary>
        public bool isAdmin { get; protected set; }

        public string EncryptionPassword { get { return UserName + Password; } }

        public List<SchoolworkClass> Projects = new List<SchoolworkClass>();

        public List<PersonClass> Students = new List<PersonClass>(); //only teachers (admins) will have students.

        public NBSettings UserSettings = new NBSettings();

        public bool ChangePassAtFirstLogin = true;

        private PersonClass()
        { }

        public PersonClass(String User, bool MakeAdmin)
        {
            _UserName = User;
            isAdmin = MakeAdmin;
        }

        public PersonClass(string Filename)
        {
            Load(Filename);
            string FileAndExtension = Path.GetFileName(Filename);
            string JustFile = Path.GetFileNameWithoutExtension(Filename);
            if(UserName != JustFile)
            {
                throw new Exception(string.Format(NB.Translate("PPF_TamperedFile"), FileAndExtension, UserName));
            }
        }
        public PersonClass(XmlNode reader)
        {
            Load(reader);
        }
        /// <summary>
        /// Try to load the specified filename. Return a new person record if it succeeds.
        /// Return null if it fails to load.  (bad password, bad file, etc)
        /// </summary>
        /// <param name="Filename">The filename to load</param>
        /// <param name="password">The password to try</param>
        /// <returns></returns>
        public static PersonClass TryLoad(string Filename, string password)
        {
            PersonClass pc = new PersonClass();
            if (pc.Load(Filename, password))
                return pc;
            return null;
        }

        public void ChangePassword(string NewPassword)
        {
            Password = NewPassword;
        }

        public void ChangePassword(bool TryOnce=false)
        {
            bool done = false;
            while (!done)
            {
                if (TryOnce) done = true; //Just attempt it once.
                string OldPassword = "";
                if (Password != "")
                {
                    OldPassword = NB.TextPromptBox(NB.Translate("PPF_OldPassword"), Properties.Resources.NBIco, true);
                    if (OldPassword != Password)
                    {
                        MessageBox.Show(NB.Translate("PPF_PassMismatch"));
                        continue;
                    }
                }
                //The passwords match, or there was no initial password.
                string Password1 = NB.TextPromptBox(NB.Translate("PPF_NewPass"), Properties.Resources.NBIco, true);
                if (Password1.Length < 4)
                {
                    MessageBox.Show(NB.Translate("PPF_PassTooShort"));
                    continue;
                }
                if (Password1 == OldPassword)
                {
                    MessageBox.Show(NB.Translate("PPF_PassDidNotChange"));
                    continue;
                }
                string Password2 = NB.TextPromptBox(NB.Translate("PPF_VerifyPass"), Properties.Resources.NBIco, true);
                if (Password1 != Password2)
                {
                    MessageBox.Show(NB.Translate("PPF_PassValidateMismatch"));
                    continue;
                }
                ChangePassword(Password1);

                if (ChangePassAtFirstLogin) ChangePassAtFirstLogin = false;//It was done.  No need to do it again.
                done = true;
            }
        }

        #region Load and Save 
        //Load and save functions
        public bool Load(string filename, string password = "")
        {
            XmlDocument xmlDoc = new XmlDocument();
            if (File.Exists(filename))
            {
                xmlDoc.Load(filename);
                if (TryToDecrypt(xmlDoc, password) != null)
                {
                    Load(xmlDoc);
                    return true;
                }
            }
            return false;
        }

        public void Load(XmlNode TheNode)
        {
            foreach (XmlNode Individual in TheNode.ChildNodes)
            {
                XmlNodeType myNodetype = Individual.NodeType;
                if (myNodetype == XmlNodeType.Element)
                {
                    switch (Individual.Name.ToLower())
                    {
                        case "edunetworkbuilderuser":
                        case "edunetworkbuilder":
                        case "user":
                            Load(Individual);
                            break;
                        case "username":
                            _UserName = Individual.InnerText;
                            break;
                        case "fullname":
                            FullName = Individual.InnerText;
                            break;
                        case "passwordhint":
                            PasswordHint = Individual.InnerText;
                            break;
                        case "altpassword":
                            AltPassword = Individual.InnerText;
                            break;
                        case "studentfilepath":
                            StudentFilePath = Individual.InnerText;
                            break;
                        case "isadmin":
                            bool isadmin = false;
                            bool.TryParse(Individual.InnerText, out isadmin);
                            isAdmin = isadmin;
                            break;
                        case "changepasswordatlogin":
                            bool changepw = false;
                            bool.TryParse(Individual.InnerText, out changepw);
                            ChangePassAtFirstLogin = changepw;
                            break;
                        case "password":
                            Password = Individual.InnerText;
                            break;
                        case "settings":
                            UserSettings = NB.Deserialize<NBSettings>(Individual.InnerText);
                            break;
                        case "project":
                        case "schoolwork":
                            SchoolworkClass SWC = new SchoolworkClass(Individual);
                            Projects.Add(SWC);
                            break;
                        case "student":
                            PersonClass PC = new PersonClass(Individual);
                            Students.Add(PC);
                            break;
                    }
                }
            }
        }

        public void Save(bool doRotation = false)
        {
            //Save to our pre-existing / pre-defined file
            string filename = Path.Combine(filepath, FileName);            
            Save(filename, doRotation);
        }

        public void Save(string filename, bool doRotation = false)
        {
            XmlWriterSettings settings = new XmlWriterSettings();
            settings.Indent = true;
            settings.NewLineOnAttributes = true;
            if (doRotation && File.Exists(filename))
                NB.MoveFileWithRotation(filename); //make a backup of the file

            //Generate a doc that has a writer attached.  All the normal save funcs work with the writer,
            //But, we can encrypt the XmlDocument
            XmlDocument doc = new XmlDocument();
            using (XmlWriter writer = doc.CreateNavigator().AppendChild())
            {
                //XmlWriter writer = XmlWriter.Create(filename, settings);

                //Now we write the file:
                writer.WriteStartDocument();
                writer.WriteStartElement("EduNetworkBuilderUser");
                writer.WriteComment("This is a user file for EduNetworkBuilder.");
                Save(writer);
                writer.WriteEndElement();
                writer.WriteEndDocument();
            }
            if(TryToEncrypt(doc) != null)
                doc.Save(filename);
            else
            {
                //We should blow up gracefully.  Not sure why we failed.
            }
        }


        public void Save(XmlWriter writer, bool AsStudent=false)
        {
            //Save the language name
            //save the number of items
            //Save all the items
            string Element = "User";
            if (AsStudent) Element = "Student";
            writer.WriteStartElement(Element);
            writer.WriteElementString("UserName", UserName);
            writer.WriteElementString("FullName", FullName);
            writer.WriteElementString("PasswordHint", PasswordHint);
            if(isAdmin) //only admins have students, so they are the only ones who need to store this path
                writer.WriteElementString("StudentFilePath", StudentFilePath);
            if (AltPassword == "") AltPassword = TrippleDESDocumentEncryption.GenMachinePW(NB.GetRandom());
            writer.WriteElementString("AltPassword", AltPassword);
            writer.WriteElementString("IsAdmin", isAdmin.ToString());
            writer.WriteElementString("ChangePasswordAtLogin", ChangePassAtFirstLogin.ToString());
            string settingsstring = NB.SerializeObject<NBSettings>(UserSettings);
            writer.WriteElementString("Settings", settingsstring);

            writer.WriteElementString("Password", Password);
            foreach (PersonClass PC in Students)
                PC.Save(writer, true); //Save as a student entry
            //Save all the devices
            foreach (SchoolworkClass One in Projects)
            {
                One.Save(writer);
            }
            writer.WriteEndElement();
        }

        private XmlDocument TryToEncrypt(XmlDocument What)
        {
            string UserPassword = UserName + Password;
            if (UserPassword == "") return null; //This should never happen
            string salt = TrippleDESDocumentEncryption.GenSalt(NB.GetRandom());

            TripleDES tDESkey = TrippleDESDocumentEncryption.GenKey(UserPassword, salt);
            TrippleDESDocumentEncryption xmlTDES = new TrippleDESDocumentEncryption(What, tDESkey);

            try
            {
                // Encrypt the "user" element.
                xmlTDES.Encrypt("User");
                //make the entries for the key
                XmlNode tNode = xmlTDES.Doc.CreateElement("EncryptedKey");
                tNode.InnerText = UserPassword;
                XmlElement inputElement = xmlTDES.Doc.GetElementsByTagName("EncryptedData")[0] as XmlElement;
                xmlTDES.Doc.DocumentElement.InsertAfter(tNode, inputElement);

                //add the salt
                tNode = xmlTDES.Doc.CreateElement("Salt");
                tNode.InnerText = salt;
                xmlTDES.Doc.DocumentElement.InsertAfter(tNode, inputElement);

                //encrypt the user key with the admin key
                xmlTDES.SetKey(AltPassword, salt);
                xmlTDES.Encrypt("EncryptedKey");

            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
                return null;
            }
            return xmlTDES.Doc;
        }

        private XmlDocument TryToDecrypt(XmlDocument What, string PassToTry)
        {
            bool first_failed = false;
            XmlElement inputElement = What.GetElementsByTagName("Salt")[0] as XmlElement;
            if (inputElement == null) return What; //No salt means it was never encrypted.  Load as normal
            string salt = inputElement.InnerText;
            // Create a new TripleDES key. 
            TripleDES tDESkey = TrippleDESDocumentEncryption.GenKey(PassToTry, salt);
            TrippleDESDocumentEncryption xmlTDES = new TrippleDESDocumentEncryption(What, tDESkey);

            //First, try to decrypt it using the user password.
            try
            {
                xmlTDES.Decrypt("User");
            }
            catch (LoginException le)
            {
                //we ignore this.  Just try it again.
                first_failed = true;
            }
            catch(Exception e) //This is bad.  Blow up and exit the function
            {
                MessageBox.Show(e.ToString());
                return null;
            }

            if (first_failed)
            {   //try a second one, decrypting the alt key and then using that.  The admin does this.
                // Create a new instance of the TrippleDESDocumentEncryption object
                // defined in this sample.
                try
                {
                    xmlTDES.Decrypt("EncryptedKey");

                    inputElement = xmlTDES.Doc.GetElementsByTagName("EncryptedKey")[0] as XmlElement;

                    string userpw = inputElement.InnerText;
                    xmlTDES.SetKey(userpw, salt);

                    xmlTDES.Decrypt("User");
                }
                catch (LoginException le)
                {
                    //Neither password worked.  Exit out so we can prompt for an appropriate password
                    return null;
                }
                catch (Exception e) //This is bad.  Blow up and exit the function
                {
                    MessageBox.Show(e.ToString());
                    return null;
                }
            }
            return xmlTDES.Doc;
        }
            #endregion

        public bool AddHomework(SchoolworkClass ToAdd)
        {
            //Check to see if we already exist
            foreach(SchoolworkClass one in Projects)
            {
                if (one.ThisID == ToAdd.ThisID) return false;
            }
            Projects.Add(ToAdd);
            if (ToAdd.ThisID >= UserSettings.NextID) UserSettings.NextID = (uint)(ToAdd.ThisID + 1);
            return true;
        }

        public bool HasHomework(SchoolworkClass ToCheck, bool compare_ID)
        {
            //Check to see if we already exist
            foreach (SchoolworkClass one in Projects)
            {
                if (compare_ID && one.ThisID == ToCheck.ThisID)
                    return true;
                else if (!compare_ID && one.Name.ToLower() == ToCheck.Name.ToLower())
                    return true;
            }
            return false;
        }

        public bool RegisterNewlySubmittedHW(SchoolworkClass ToCheck)
        {
            //Check to see if we already exist
            foreach (SchoolworkClass one in Projects)
            {
                if (one.ThisID == ToCheck.ThisID)
                {
                    if (one.SaveDate == ToCheck.SaveDate)
                        return false; //They were the same
                }
            }
            //If we get here, we do not yet have it. Add it to the user.
            Projects.Add(ToCheck.Clone());
            return true;
        }

        public void LoadHomeworkFile(string HomeworkFile)
        {
            if (!File.Exists(HomeworkFile)) return; //The file does not exist
            if (! (Path.GetExtension(HomeworkFile).ToLower() == ".enbh")) return; //It is not a homework file
            List<SchoolworkClass> WhatWeRead = SchoolworkClass.LoadMultiple(HomeworkFile);
            if(WhatWeRead.Count > 0) //We have something to import
            {
                bool IsOK = true;
                //Verify that all the homeworks are OK to load
                foreach(SchoolworkClass one in WhatWeRead)
                {
                    //if one is already submitted, reject it
                    if(one.IsSumbitted || one.IsGraded)
                    {
                        IsOK = false;
                    }
                }
                //Load them
                if(IsOK)
                {
                    int totalHW =0;
                    int Skipped = 0;
                    int imported = 0;
                    if (isAdmin)
                    {
                        Dictionary<string, bool> InitialList = new Dictionary<string, bool>();
                        //use a check-box list
                        foreach (SchoolworkClass one in WhatWeRead)
                        {
                            //Use due date and name + already exists (name / ID)
                            string Info = one.DueDate.ToShortDateString();
                            Info += "\t" + one.Name;
                            if (HasHomework(one, true)) Info += "    * " + string.Format(NB.Translate("PC_HWHasID"),one.ThisID);
                            if (HasHomework(one, false)) Info += "    ** " + string.Format(NB.Translate("PC_HWHasName"));
                            InitialList.Add(Info, false);
                        }
                        Dictionary<string, bool> ResponseList = NB.CheckPromptBox(InitialList, "Import", Properties.Resources.NBIco);

                        if (ResponseList == null) return; //They canceled out.

                        int index = 0;
                        foreach(string One in ResponseList.Keys)
                        {
                            totalHW++;
                            if (ResponseList[One])
                            { //We are supposed to import it.
                                if (HasHomework(WhatWeRead[index], true))
                                { //We already have this ID.  Get a new ID
                                    WhatWeRead[index].ChangeID(this); //Pass in this user, whoever we are
                                }
                                //Now we can add it without issue.
                                AddHomework(WhatWeRead[index]);
                                imported++;
                            }
                            else
                                Skipped++;
                            index++;
                        }
                        //they check if they want to import
                        //We never overwrite, we make a new one, with new ID
                    }
                    else
                    {
                        foreach (SchoolworkClass one in WhatWeRead)
                        {
                            totalHW++;
                        
                            if (AddHomework(one))
                            {
                                imported++;
                            }
                            else
                            {
                                Skipped++;
                            }
                        }
                    }
                    MessageBox.Show(string.Format(NB.Translate("PC_HWFileImportStats"),"\n\t" + totalHW, "\n\t" + imported, "\n\t" + Skipped));
                }
                else
                {
                    MessageBox.Show(NB.Translate("PC_AlreadySubmittedOrGraded"));
                }
            }
        }

        /// <summary>
        /// Organize the projects into a nice project tree.
        /// </summary>
        /// <returns></returns>
        public TreeNode WorkTree(TreeNode theTop=null, bool ForTeacher = false)
        {
            //Sort by due date, and within the due dates, sort by save date            
            Projects.Sort((q, p) => p.Compare(q));

            List<SchoolworkClass> DrawFrom = new List<SchoolworkClass>();
            List<SchoolworkClass> MasterList = new List<SchoolworkClass>();
            bool SomethingWasBolded = false; //Set this to true if we make something bold

            DrawFrom.AddRange(Projects);
            for (int i = DrawFrom.Count - 1; i >= 0; i--)
            {
                if (DrawFrom[i].isMaster)
                {
                    MasterList.Add(DrawFrom[i]);
                    DrawFrom.RemoveAt(i);
                }
            }
            //Now, all the masters are in Master, and submitted projects are in DrawFrom.

            TreeNode Node;
            TreeNode Top;
            TreeNode Master;
            //We have a class tree
            if (theTop == null)
            {
                Top = new TreeNode(NB.Translate("PPF_Class"));
                Top.Tag = "Class"; //Do not translate this
            }
            else
            {
                Top = theTop;
            }
            Font tFont = NB.GetFont();

            foreach (SchoolworkClass MasterSW in MasterList)
            {
                int count = 0;
                int ungraded = 0;
                Master = new TreeNode(MasterSW.Name);
                Master.Tag = MasterSW;

                for (int i = DrawFrom.Count -1; i>=0; i--)
                {
                    if(DrawFrom[i].ThisID == MasterSW.ThisID)
                    {
                        Node = new TreeNode(DrawFrom[i].Name);
                        Node.Tag = DrawFrom[i];
                        string tooltip = FullName + "\n";
                        tooltip += DrawFrom[i].Name + "\n";
                        tooltip += DrawFrom[i].Description + "\n";
                        if(DrawFrom[i].IsSumbitted)
                            tooltip += "Submitted: " + DrawFrom[i].SaveDate.ToShortDateString() + "\n";
                        if(DrawFrom[i].theProject != null)
                        {
                            List<string> tMessages = DrawFrom[i].theProject.GetMessageStrings();
                            foreach(string one in tMessages)
                            {
                                tooltip += one + "\n";
                            }
                        }
                        Node.ToolTipText = tooltip;
                        Master.Nodes.Add(Node);
                        if (ForTeacher && !DrawFrom[i].IsGraded) //Bold ungraded homework
                        {
                            Node.NodeFont = new Font(tFont.FontFamily, tFont.Size, FontStyle.Bold);
                            SomethingWasBolded = true;
                            ungraded++;
                        }
                        count++; //How many submitted versions are there
                    }
                }
                if(!ForTeacher && count ==0) //bold homework that has not yet been submitted
                {
                    Master.NodeFont = new Font(tFont.FontFamily, tFont.Size, FontStyle.Bold);
                    SomethingWasBolded = true;
                } else if(ForTeacher && ungraded > 0) //If we have ungraded submissions
                {
                    Master.NodeFont = new Font(tFont.FontFamily, tFont.Size, FontStyle.Bold);
                    SomethingWasBolded = true;
                }
                Top.Nodes.Add(Master);
            }

            if(ForTeacher && SomethingWasBolded)
                Top.NodeFont = new Font(tFont.FontFamily, tFont.Size, FontStyle.Bold);
            return Top;
        }

        /// <summary>
        /// Return the student with the specified UserName
        /// </summary>
        /// <param name="UserName">The user name to find</param>
        /// <returns>The specified person, or null if no such person exists.</returns>
        public PersonClass StudentWithTheUserName(string UserName)
        {
            if (!isAdmin) return null; //Students do not store other student records
            foreach(PersonClass PC in Students)
            {
                if (PC.UserName == UserName) return PC;
            }
            return null;
        }

        /// <summary>
        /// Import the settings from a student record
        /// </summary>
        /// <param name="ImportedStudent">the student we are importing from</param>
        /// <returns>The count of homeworks we added.  -1 if there is an error</returns>
        public int TeacherImportStudentHomework(PersonClass ImportedStudent)
        {
            if (!isAdmin) return -1;
            PersonClass StoredStudent = StudentWithTheUserName(ImportedStudent.UserName);
            if (StoredStudent == null) return -1;

            int NumSubmitted = 0;

            //Retrieve all the stored settings, in case we need to re-write their file
            StoredStudent.UserSettings = ImportedStudent.UserSettings;

            //Pull out their password, in case we want to retrieve it.  (sigh)
            StoredStudent.Password = ImportedStudent.Password;

            //   loop through all networks
            foreach (SchoolworkClass SWC in ImportedStudent.Projects)
            {
                //   Find any that are submitted, which are not in the admin file
                if (StoredStudent.RegisterNewlySubmittedHW(SWC))
                {
                    //   copy them into the admin file
                    if(SWC.IsSumbitted) //Only report submitted homeworks back.
                        NumSubmitted++;
                }
            }
            return NumSubmitted;
        }

    }
}
