// Copyright (C) 2008 panacoran <panacoran@users.sourceforge.jp>
// 
// This program is part of Protra.
//
// Protra is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, see <http://www.gnu.org/licenses/>.
// 
// $Id: Buffer.cs 306 2010-03-19 13:21:22Z panacoran $

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;

namespace Protra.Lib.Lang
{
    /// <summary>
    /// ファイルの読み込み位置を保持する構造体。
    /// </summary>
    struct Position
    {
        private StreamReader sr;
        private string filename;
        private int lineNo;

        /// <summary>
        /// StreamReaderを取得あるいは設定する。
        /// </summary>
        public StreamReader Reader
        {
            get { return sr; }
            set { sr = value; }
        }

        /// <summary>
        /// ファイル名を取得あるいは設定する。
        /// </summary>
        public string Filename
        {
            get { return filename; }
            set { filename = value; }
        }

        /// <summary>
        /// 行番号を取得あるいは設定する。
        /// </summary>
        public int LineNo
        {
            get { return lineNo; }
            set { lineNo = value; }
        }
    }

    /// <summary>
    /// 空白のスキップと#include/require文の処理を行う行バッファ
    /// </summary>
    public class Buffer
    {
        static Regex skipRegex = new Regex(@"\A\s*(?://.*|#.*)?", RegexOptions.Compiled);
        static Regex contRegex = new Regex(@"\A\s[_\\]\z", RegexOptions.Compiled);
        static Regex includeRegex = new Regex(@"\A\s*(?:#include\s*<(.*)>|require\s*""(.*)"")", RegexOptions.Compiled);
        static private Encoding encoding = Encoding.GetEncoding("Shift_JIS");
        private Position pos;
        private string line;
        private int column;
        Stack<Position> posStack;

        /// <summary>
        /// ファイル名を取得する。
        /// </summary>
        public string Filename
        {
            get { return pos.Filename; }
        }

        /// <summary>
        /// 行番号を取得する。
        /// </summary>
        public int LineNo
        {
            get { return pos.LineNo; }
        }

        /// <summary>
        /// 行バッファを取得する。
        /// </summary>
        public string Line
        {
            get { return line; }
        }

        /// <summary>
        /// 現在の読み取り位置を取得する。
        /// </summary>
        public int Column
        {
            get { return column; }
            set { column = value; }
        }

        /// <summary>
        /// コンストラクタ。
        /// </summary>
        /// <param name="file">読み込むファイルのパス名を指定する。</param>
        public Buffer(string file)
        {
            try
            {
                pos.Reader = new StreamReader(file, encoding);
            }
            catch (IOException)
            {
                throw new ParseException("Can't open file --- " + file, null);
            }
            pos.Filename = file;
            posStack = new Stack<Position>();
        }

        /// <summary>
        /// 次の空白ではない文字まで読み込み位置を進める。
        /// </summary>
        public void Next()
        {
            while (true)
            {
                Match m;
                if (line == null)
                {
                    ReadLine();
                    if (line == null)
                        break;
                    m = includeRegex.Match(line, column);
                    if (m.Success)
                    {
                        OpenIncludeFile((m.Groups[1].Success ? m.Groups[1].Value : m.Groups[2].Value));
                        line = null;
                        continue;
                    }
                }
                m = contRegex.Match(line, column, line.Length - column);
                if (m.Success)
                {
                    // _による継続行の処理。
                    line = null;
                    continue;
                }
                m = skipRegex.Match(line, column, line.Length - column); // 必ず成功する。
                if (m.Length == line.Length)
                {
                    // 空行は読み飛ばす。
                    line = null;
                    continue;
                }
                column += m.Length;
                if (column >= line.Length)
                {
                    if (line[column - m.Length - 1] != ';')
                    {
                        // ';'で終わっていない改行には;を挿入する。
                        line = ";";
                        column = 0;
                    }
                    else
                    {
                        line = null;
                        continue;
                    }
                }
                break;
            }
        }

        private void ReadLine()
        {
            while ((line = pos.Reader.ReadLine()) == null)
            {
                pos.Reader.Close();
                if (posStack.Count == 0)
                    return;
                pos = posStack.Pop();
            }
            pos.LineNo++;
            column = 0;
        }

        private void OpenIncludeFile(string name)
        {
            posStack.Push(pos);
            name = name.Replace('/', Path.DirectorySeparatorChar);
            string lib = Global.DirLib + Path.DirectorySeparatorChar + name + ".pt";
            try
            {
                pos.Reader = new StreamReader(lib, encoding);
            }
            catch (IOException)
            {
                Token token = new Token();
                token.LineNo = pos.LineNo;
                token.Filename = pos.Filename;
                throw new ParseException("Can't open file --- " + lib, token);
            }
            pos.LineNo = 0;
            pos.Filename = lib;
        }

        /// <summary>
        /// 読んでいるファイルをクローズする。
        /// </summary>
        public void Close()
        {
            pos.Reader.Close();
            foreach (Position p in posStack)
                p.Reader.Close();
        }
    }
}
