using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;

using Taśma = char[]; //TODO: zad1

namespace MaszynaTuringa
{
    public class TuringException : Exception
    {
        public TuringException(string? message = null, Exception? innerException = null)
            : base(message, innerException)
        {
        }
    }

    public class Turing
    {
        public Taśma taśma { private set; get; }
        public char stanGłowicy { private set; get; }
        public int położenieGłowicy { private set; get; }
        public string[] program { private set; get; }

        //AABASqAAKAAK

        /*
        //POCO
        class StanMaszyny
        {
            public char[] Taśma;
            public char StanGłowicy;
            public int PołożenieGłowicy;
        }
        */

        /*
        public static string CzytajTaśmę(string ścieżkaPlikuTaśmy)
        {
            return ""; //TODO: zad2 - Jakub
        }

        public static string[] CzytajProgram(string ścieżkaPlikuProgramu)
        {
            return new string[1] { "" }; //TODO: zad3 - Jakub
        }
        */

        public static string CzytajTaśmę(string ścieżkaPlikuTaśmy)
        {
            var taśma = File.ReadAllText(ścieżkaPlikuTaśmy);
            if (taśma is null)
            {
                throw new TuringException("Nie udało się wczytać taśmy");
            }
            return taśma;
        }

        public static string[] CzytajProgram(string ścieżkaPlikuProgramu)
        {
            try
            {
                string[] progam = File.ReadAllLines(ścieżkaPlikuProgramu);
                /*if (progam is null)
                {
                    throw new TuringException("Nie udało się wczytać programu");
                }*/
                return progam;
            }
            catch(Exception exc)
            {
                throw new TuringException("Błąd odczytu pliku program", exc);
            }
        }
       
        public static void SprawdźTaśmę(char[] taśma)
        {
            
        }

        /*
        public static (Taśma taśma, char stanGłowicy, int położenieGłowicy) ParsujZapisTaśmy(string zapisTaśmy)
        {
            return (new char[1] { '@' }, '@', 0); //TODO: zad5 - Jan
        }
        */

        public static (Taśma taśma, char stanGłowicy, int położenieGłowicy) 
            ParsujZapisTaśmy(string zapisTaśmy) // Wraz z sprawdzeniem
        {
            List<char> nowaTaśma = [];
            char nowyStanGłowicy = '\0';
            int nowePolozenieGlowicy = 0;
            for (int i = 0; i < zapisTaśmy.Length; i++)
            {
                if (zapisTaśmy[i] == 'L' || zapisTaśmy[i] == 'R')
                {
                    throw new TuringException("Błąd w zapisie taśmy");
                }
                if (char.IsUpper(zapisTaśmy[i]))
                {
                    nowaTaśma.Add(zapisTaśmy[i]);
                }

                if (char.IsLower(zapisTaśmy[i]))
                {
                    nowePolozenieGlowicy = i;
                    nowyStanGłowicy = zapisTaśmy[i];
                }
            }
            //Taśma nowaTaśmaAsTaśma = nowaTaśma.ToArray();
            Taśma nowaTaśmaAsTaśma = [.. nowaTaśma];
            return (nowaTaśmaAsTaśma, nowyStanGłowicy, nowePolozenieGlowicy);
        }

        public static bool SprawdźStanGłowicy(char stanGłowicy, int położenieGłowicy, int długośćTaśmy)
        {
            return !char.IsLower(stanGłowicy) || !(położenieGłowicy >= 0 && położenieGłowicy < długośćTaśmy);
        }

        public static void SprawdźProgram(string[] program)
        {
            List<string> sprawdzonePary = new List<string>();

            if (program.Length == 0) throw new TuringException("Program nie zawiera żadnej linii");
            foreach (string linia in program)
            {
                if (string.IsNullOrEmpty(linia)) throw new TuringException("Pusta linia programu");
                if (linia.Length != 4) throw new TuringException("Niepoprawna długość linii programu");   //długość 4

                if ((!char.IsLower(linia[0])) || (!char.IsUpper(linia[1])) || (!char.IsUpper(linia[2])) || (!char.IsLower(linia[3]))) throw new TuringException("Niepoprawna składnia czwórki"); //składnia qAAq

                if ((linia[0] == linia[3]) && (linia[1] == linia[2])) throw new TuringException("Palindrom: linia programu nie wprowadzająca zmian"); //SAAS

                string polowa = linia.Substring(0, 2);
                if (sprawdzonePary.Contains(polowa)) throw new TuringException("Powtarzające się programy");
                sprawdzonePary.Add(polowa);
            }
        }


        /*
        public void ZapiszStanMaszyny(string ścieżkaPliku)
        {
            throw new NotImplementedException(); //TODO: zad7 - Emilia
        }
        */

        /*
        public void ZapiszStanMaszyny(string ścieżkaPliku)
        {
            try
            {
                string taśmaTekst = new string(taśma);
                taśmaTekst = taśmaTekst.Insert(położenieGłowicy, char.ToLower(stanGłowicy).ToString());
                File.WriteAllText(ścieżkaPliku, taśmaTekst);
            }
            catch (Exception ex)
            {
                throw new TuringException("Błąd podczas zapisywania stanu maszyny", ex);
            }
        }
        */

        public void ZapiszStanMaszyny(string ścieżkaPliku)
        {
            try
            {
                StringBuilder sb = new StringBuilder(new string(taśma));
                sb.Insert(położenieGłowicy, char.ToLower(stanGłowicy));
                File.WriteAllText(ścieżkaPliku, sb.ToString());
            }
            catch (Exception ex)
            {
                throw new TuringException("Błąd podczas zapisywania stanu maszyny", ex);
            }
        }

        public Turing(Taśma taśma, char stanGłowicy, int położenieGłowicy, string[] program)
        {
            if (taśma == null) throw new TuringException("Argument taśma nie może być pusty");
            if (program == null) throw new TuringException("Argument program nie może być pusty");

            try
            {
                SprawdźTaśmę(taśma);
                this.taśma = taśma;
                this.stanGłowicy = stanGłowicy;
                this.położenieGłowicy = położenieGłowicy;
            }
            catch(Exception exc)
            {
                throw new TuringException("Niepoprawna taśma", exc);
            }

            try
            {
                SprawdźProgram(program);
                this.program = program;
            }
            catch (Exception exc)
            {
                throw new TuringException("Błędny program", exc);
            }
        }

        //pseudokonstruktor
        public static Turing Load(string ścieżkaTaśmy, string ścieżkaProgramu)
        {
            string zapisTaśmy = CzytajTaśmę(ścieżkaTaśmy);
            var stanMaszyny = ParsujZapisTaśmy(zapisTaśmy);
            Taśma taśma = stanMaszyny.taśma;
            char stanGłowicy = stanMaszyny.stanGłowicy;            
            int położenieGłowicy = stanMaszyny.położenieGłowicy;
            
            //TODO: do zmiany na wyjątek wewnętrzny
            if (SprawdźStanGłowicy(stanGłowicy, położenieGłowicy, taśma.Length)) throw new Exception("Błędny stan głowicy");
            
            string[] program = CzytajProgram(ścieżkaProgramu);
            Turing maszyna = new Turing(taśma, stanGłowicy, położenieGłowicy, program);
            return maszyna;
        }

        #region Procesor
        /*
        private string? znajdźWłaściwąLinięProgramu()
        {
            return null;
        }

        public void wykonajWłaściwąLinięProgramu(string czwórka)
        {

        }

        public void WykonajProgram()
        {
            string? czwórka;
            while ((czwórka = znajdźWłaściwąLinięProgramu())!=null)
            {
                wykonajWłaściwąLinięProgramu(czwórka);                
            }
        }
        */

        private string? znajdźWłaściwąLinięProgramu()
        {
            if (położenieGłowicy < 0 || położenieGłowicy >= taśma.Length)
            {
                return null;
            }

            char obecnyZnakNaTaśmie = taśma[położenieGłowicy];

            foreach (string linia in program)
            {
                if (linia[0] == stanGłowicy && linia[1] == obecnyZnakNaTaśmie)
                {
                    return linia;
                }
            }

            return null;
        }

        public void wykonajWłaściwąLinięProgramu(string czwórka)
        {
            if (czwórka == null || czwórka.Length != 4)
            {
                throw new TuringException("Nieprawidłowa czwórka programu");
            }            

            char nowyZnak = czwórka[2]; //nowaWartośćKomórkiTaśmyLubInstrukcjaPrzesunięcia
            char nowyStanGłowicy = czwórka[3];

            switch(nowyZnak)
            {
                case 'L':
                    położenieGłowicy--;
                    break;
                case 'R':
                    położenieGłowicy++;
                    break;
                default:
                    taśma[położenieGłowicy] = nowyZnak;
                    break;
            }            

            stanGłowicy = nowyStanGłowicy;
        }

        public void WykonajProgram()
        {
            string? czwórka;
            while ((czwórka = znajdźWłaściwąLinięProgramu()) != null)
            {
                wykonajWłaściwąLinięProgramu(czwórka);
                //Console.WriteLine($"stan głowicy: {stanGłowicy}, pozycja głowicy: {położenieGłowicy}, taśma: {new string(taśma)}");
                onPolecenieWykonane();
            }
        }
        #endregion

        #region Zdarzenia

        public class PolecenieWykonaneEventArgs : EventArgs
        {
            public Taśma Taśma; //TODO: zadbać o głęboką kopię
            public char StanGłowicy;
            public int PołożenieGłowicy;

            public string TaśmaAsString
            {
                get
                {
                    return new string(Taśma);
                }
            }

            public override string ToString()
            {
                StringBuilder sb = new StringBuilder(new string(Taśma));
                sb.Insert(PołożenieGłowicy, char.ToLower(StanGłowicy));
                return sb.ToString();
            }
        }

        public event EventHandler<PolecenieWykonaneEventArgs> PolecenieWykonane;

        protected void onPolecenieWykonane()
        {
            if (PolecenieWykonane != null)
                PolecenieWykonane(this, new PolecenieWykonaneEventArgs()
                {
                    PołożenieGłowicy = this.położenieGłowicy,
                    StanGłowicy = this.stanGłowicy,
                    Taśma = (char[])this.taśma.Clone()
                });
        }
        #endregion
    }
}
