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

namespace vyhodnoceni
{
    public class Network
    {
        private static Dictionary<string, Network> networks = new Dictionary<string, Network> { };

        // vytvori sit nebo vrati drive vytvorenou
        public static Network GetNetwork(string netFile)
        {
            if (!networks.ContainsKey(netFile))
            {
                networks[netFile] = new Network(netFile);
            }

            return networks[netFile];
        }

        // hlavni vstupy se chovaji stejne jako vystupy uvnitr hradel (a naopak)
        private List<Output> globalInputs = new List<Output> { };
        private List<Input> globalOutputs = new List<Input> { };

        private List<Gate> gates = new List<Gate> { };

        // seznamy vsech vstupu a vystupu
        private List<Input> inputs = new List<Input> { };
        private Dictionary<string, Output> outputs = new Dictionary<string, Output> { };

        // pro testovani
        public bool Testing { get; set; }
        public string OutputData { get; private set; }


        // zpracuje xml soubor a vytvori strukturu objektu
        private Network(string netFile)
        {
            Testing = false;

            var xml = new XmlDocument();
            xml.Load(netFile);

            foreach (XmlElement item in xml.ChildNodes[0].ChildNodes)
            {
                switch (item.Name)
                {
                    case "input":
                        globalInputs.Add(CreateOutput(item));
                        break;
                    case "output":
                        globalOutputs.Add(CreateInput(item));
                        break;
                    case "gate":
                        gates.Add(CreateGate(item));
                        break;
                    default:
                        throw new NotSupportedException("Tag " + item.Name + " neni podporovany.");
                }
            }

            // spoji vstupy s prislusnimy vystupy
            foreach (var input in inputs)
            {
                string id = input.GetId();
                if (!outputs.ContainsKey(id)) throw new Exception("Neexistuje vystupni prvek s id = '" + id + "'");
                input.SetOutput(outputs[id]);
            }
        }

        // zpracuje vstupni soubor
        public void ProcessFile(string inputFile)
        {
            var reader = new StreamReader(inputFile);

            while (!reader.EndOfStream)
            {
                string inputLine = reader.ReadLine().Trim();
                string outputLine = ProcessLine(inputLine);

                if (outputLine != null)
                {
                    if (!Testing)
                    {
                        Console.WriteLine(outputLine);
                    }
                    else
                    {
                        OutputData += outputLine + "\r\n";
                    }
                }
                else
                {
                    Console.WriteLine("");
                }
            }

            reader.Close();
        }

        // zpracuje radek vstupniho souboru
        private string ProcessLine(string inputLine)
        {
            if (inputLine.Length != globalInputs.Count) return null;

            var data = new bool[globalInputs.Count];
            for (int i = 0; i < globalInputs.Count; i++)
            {
                data[i] = CharToBool(inputLine[i]);
            }

            var outputData = ProcessData(data);

            var outputLine = new StringBuilder(globalOutputs.Count);
            foreach (var item in outputData)
            {
                outputLine.Append(BoolToChar(item));
            }

            return outputLine.ToString();
        }

        // zpracuje vstupni data
        public bool[] ProcessData(bool[] data)
        {
            ResetNetwork();

            // nastavi pocatecni hodnoty
            for (int i = 0; i < globalInputs.Count; i++)
            {
                globalInputs[i].SetValue(data[i]);
            }

            bool done = false;
            while (!done)
            {
                // spocita hodnoty hradel, pokud je to mozne
                bool changed = false;
                foreach (var gate in gates)
                {
                    if (!gate.HasValue())
                    {
                        changed |= gate.Calculate();
                    }
                }

                if (!changed)
                {
                    // nic se nezmenilo, vypocet je tedy snad hotovy
                    done = true;
                    foreach (var output in globalOutputs)
                    {
                        done &= output.HasValue();
                        if (!done) break;
                    }

                    if (!done)
                    {
                        // vypocet neni hotovy
                        throw new Exception("Spatna sit, nejde vyresit");
                    }
                }
            }

            // posbira vystupni data
            var outputData = new bool[globalOutputs.Count];
            for (int i = 0; i < globalOutputs.Count; i++)
            {
                outputData[i] = globalOutputs[i].GetValue();
            }

            return outputData;
        }

        // pripravi sit pro dalsi vypocet
        private void ResetNetwork()
        {
            foreach (var item in gates)
            {
                item.Reset();
            }
            foreach (var item in outputs)
            {
                item.Value.Reset();
            }
        }

        private char BoolToChar(bool value)
        {
            return value ? '1' : '0';
        }

        private bool CharToBool(char ch)
        {
            switch (ch)
            {
                case '0':
                    return false;
                case '1':
                    return true;
                default:
                    throw new NotSupportedException("Znak '" + ch + "' ve vstupnich datech neni podporovan.");
            }
        }

        private Input CreateInput(XmlElement item)
        {
            string id = item.Attributes["id"].Value;
            var input = new Input(id);
            inputs.Add(input);
            return input;
        }

        private Output CreateOutput(XmlElement item)
        {
            string id = item.Attributes["id"].Value;
            var output = new Output(id);
            outputs.Add(id, output);
            return output;
        }

        private Gate CreateGate(XmlElement item)
        {
            string type = item.Attributes["type"].Value;
            type = type.ToLower();

            // vytvori branu
            Gate gate = null;
            switch (type)
            {
                case "and":
                    gate = new AndGate();
                    break;
                case "or":
                    gate = new OrGate();
                    break;
                case "xor":
                    gate = new XorGate();
                    break;
                case "not":
                    gate = new NotGate();
                    break;
                default:
                    gate = new ComplexGate(type);
                    break;
            }

            // prida brane vstupy a vystupy
            foreach (XmlElement child in item)
            {
                switch (child.Name)
                {
                    case "input":
                        gate.Add(CreateInput(child));
                        break;
                    case "output":
                        gate.Add(CreateOutput(child));
                        break;
                    default:
                        throw new NotSupportedException("Tag " + child.Name + " neni podporovany.");
                }
            }

            return gate;
        }
    }
}