// Copyright (C) 2008, 2013 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 455 2013-06-08 11:52:33Z panacoran $

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

namespace Protra.Lib.Lang
{
    /// <summary>
    /// ファイルの読み込み位置を保持する構造体。
    /// </summary>
    internal struct Position
    {
        /// <summary>
        /// StreamReaderを取得あるいは設定する。
        /// </summary>
        public StreamReader Reader { get; set; }

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

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

    /// <summary>
    /// 空白のスキップと#include/require文の処理を行う行バッファ
    /// </summary>
    public class Buffer
    {
        private static readonly Regex SkipRegex = new Regex(@"\A\s*(?://.*|#.*)?", RegexOptions.Compiled);
        private static readonly Regex ContRegex = new Regex(@"\A\s[_\\]\z", RegexOptions.Compiled);
        private static readonly Regex IncludeRegex = new Regex(@"\A\s*(?:#include\s*<(.*)>|require\s*""(.*)"")", RegexOptions.Compiled);
        private static readonly Encoding Encoding = Encoding.GetEncoding("Shift_JIS");
        private Position _pos;
        private string _line;
        private int _column;
        private readonly 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);
            var lib = Global.DirLib + Path.DirectorySeparatorChar + name + ".pt";
            try
            {
                _pos.Reader = new StreamReader(lib, Encoding);
            }
            catch (IOException)
            {
                var token = new Token {LineNo = _pos.LineNo, 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 (var p in _posStack)
                p.Reader.Close();
        }
    }
}