using System;
using System.Collections.Generic;
using System.Text;

namespace PinyinModel
{
    internal partial class SyllableGraph : Graph
    {
        private const int pinyin_max_length = 6;
        private class VertexData
        {
            public bool accessed_forward = false;
            public bool accessed_backward = false;
            public int shrinked_id = -1;
        }

        private string pinyin_string;
        private PinyinSyllable valid_syllables;

        public int Count
        {
            get { return vertex_count; }
        }

        internal SyllableGraph(string pinyin_string, PinyinSyllable valid_syllables)
        {
            Console.Write("Making syllable graph... ");
            this.pinyin_string = pinyin_string;
            this.valid_syllables = valid_syllables;
            MakeGraph();
            Console.WriteLine("Done.");
        }

        private void MakeGraph()
        {
            vertex_count = pinyin_string.Length + 1;
            InitializeVertex();
            for (int i = 0; i < vertex_count; i++)
            {
                vertex[i].data = new VertexData();
            }

            for (int i = 0; i < pinyin_string.Length; ++ i)
            {
                for (int j = 1; j <= pinyin_max_length && i + j <= pinyin_string.Length; ++j)
                {
                    string pyslice = pinyin_string.Substring(i, j);
                    if (valid_syllables.IsValidSyllable(pyslice))
                    {
                        Edge e = AddEdge(vertex[i], vertex[i + j]);
                        e.data = new string(pyslice.ToCharArray());
                    }
                }
            }

            ShrinkGraph();
        }

        private void ShrinkGraph()
        {
            SearchForward(vertex[0]);

            VertexData venddata = vertex[vertex_count - 1].data as VertexData;
            if (!venddata.accessed_forward)
            {
                throw new Exception("InvalidSyllable");
            }

            SearchBackward(vertex[vertex_count - 1]);

            int valid_vertex_count = 0;
            for (int i = 0; i < vertex_count; i++)
            {
                VertexData vdata = vertex[i].data as VertexData;
                if (vdata.accessed_forward && vdata.accessed_backward)
                    vdata.shrinked_id = valid_vertex_count++;
            }

            Vertex[] shrinked = new Vertex[valid_vertex_count];
            for (int i = 0; i < valid_vertex_count; i++)
            {
                shrinked[i] = new Vertex(i);
                shrinked[i].data = new VertexData();
                (shrinked[i].data as VertexData).shrinked_id = i;
            }

            for (int i = 0; i < vertex_count; i++)
            {
                VertexData vdata = vertex[i].data as VertexData;
                if (vdata.shrinked_id >= 0)
                {
                    foreach (Edge e in vertex[i].to)
                    {
                        VertexData evdata = e.to.data as VertexData;
                        if (evdata.shrinked_id >= 0)
                        {
                            Edge e_new = AddEdge(shrinked[vdata.shrinked_id], shrinked[evdata.shrinked_id]);
                            e_new.data = e.data;
                        }
                    }
                }
            }
            vertex = shrinked;
            vertex_count = valid_vertex_count;
        }

        private void SearchForward(Vertex v)
        {
            VertexData vdata = v.data as VertexData;
            vdata.accessed_forward = true;
            foreach (Edge e in v.to)
            {
                VertexData evdata = e.to.data as VertexData;
                if (!evdata.accessed_forward)
                {
                    SearchForward(e.to);
                }
            }
        }

        private void SearchBackward(Vertex v)
        {
            VertexData vdata = v.data as VertexData;
            vdata.accessed_backward = true;
            foreach (Edge e in v.from)
            {
                VertexData evdata = e.from.data as VertexData;
                if (!evdata.accessed_backward)
                {
                    SearchBackward(e.from);
                }
            }
        }

        private List<PinyinSequence> pinyin_sequences;
        private List<string> pinyin_sequence;

        internal PinyinSequence[] GetPinyinSequences(int max_length)
        {
            pinyin_sequences = new List<PinyinSequence>();
            pinyin_sequence = new List<string>();

            for (int i = 0; i < vertex_count; i++)
            {
                MakePinyinSequence(vertex[i], max_length, i);
            }
            return pinyin_sequences.ToArray(); 
        }

        private void MakePinyinSequence(Vertex v, int limit, int from)
        {
            foreach (Edge e in v.to)
            {
                pinyin_sequence.Add(e.data as string);

                pinyin_sequences.Add(new PinyinSequence(
                    pinyin_sequence.ToArray(),
                    from,
                    (e.to.data as VertexData).shrinked_id
                ));
                if (limit > 1)
                {
                    MakePinyinSequence(e.to, limit - 1, from);
                }

                pinyin_sequence.RemoveAt(pinyin_sequence.Count - 1);
            }
        }

        public override string ToString()
        {
            StringBuilder buffer = new StringBuilder();
            foreach (Edge e in this.edges)
            {
                Vertex vs = e.from;
                Vertex vt = e.to;
                int vsid = (vs.data as VertexData).shrinked_id;
                int vtid = (vt.data as VertexData).shrinked_id;

                if (vsid == -1 || vtid == -1)
                    continue;

                buffer.AppendFormat("{0} {1} \"{2}\"",
                    vsid,
                    vtid,
                    e.data as string);
                buffer.AppendLine();
            }
            return buffer.ToString();
        }
    }
}
