// ReSharper disable InconsistentNaming

namespace dotless.Core.Parser
{
    using Exceptions;
    using Importers;
    using Infrastructure;
    using Stylizers;
    using Tree;

    //
    // less.js - parser
    //
    //    A relatively straight-forward predictive parser.
    //    There is no tokenization/lexing stage, the input is parsed
    //    in one sweep.
    //
    //    To make the parser fast enough to run in the browser, several
    //    optimization had to be made:
    //
    //    - Instead of the more commonly used technique of slicing the
    //      input string on every match, we use global regexps (/g),
    //      and move the `lastIndex` pointer on match, foregoing `slice()`
    //      completely. This gives us a 3x speed-up.
    //
    //    - Matching on a huge input is often cause of slowdowns. 
    //      The solution to that is to chunkify the input into
    //      smaller strings.
    //
    //    - In many cases, we don't need to match individual tokens;
    //      for example, if a value doesn't hold any variables, operations
    //      or dynamic references, the parser can effectively 'skip' it,
    //      treating it as a literal.
    //      An example would be '1px solid #000' - which evaluates to itself,
    //      we don't need to know what the individual components are.
    //      The drawback, of course is that you don't get the benefits of
    //      syntax-checking on the CSS. This gives us a 50% speed-up in the parser,
    //      and a smaller speed-up in the code-gen.
    //
    //
    //    Token matching is done with the `Match` function, which either takes
    //    a terminal string or regexp, or a non-terminal function to call.
    //    It also takes care of moving all the indices forwards.
    //
    //
    public class Parser
    {
        public Tokenizer Tokenizer { get; set; }
        public IStylizer Stylizer { get; set; }
        public string FileName { get; set; }
        public bool Debug { get; set; }

        public string CurrentDirectory
        {
            get { return Importer.CurrentDirectory; }
            set { Importer.CurrentDirectory = value; }
        }

        private INodeProvider _nodeProvider;
        public INodeProvider NodeProvider
        {
            get { return _nodeProvider ?? (_nodeProvider = new DefaultNodeProvider()); }
            set { _nodeProvider = value; }
        }

        private IImporter _importer;
        public IImporter Importer
        {
            get { return _importer; }
            set
            {
                _importer = value;
                _importer.Parser = () => new Parser(Tokenizer.Optimization, Stylizer, _importer)
                {
                    NodeProvider = NodeProvider,
                    Debug = Debug,
                    CurrentDirectory = CurrentDirectory,
                    StrictMath = StrictMath
                };
            }
        }

        public bool StrictMath { get; set; }

        private const int defaultOptimization = 1;
        private const bool defaultDebug = false;

        public Parser()
            : this(defaultOptimization, defaultDebug)
        {
        }

        public Parser(bool debug)
            : this(defaultOptimization, debug)
        {
        }

        public Parser(int optimization)
            : this(optimization, new PlainStylizer(), new Importer(), defaultDebug)
        {
        }

        public Parser(int optimization, bool debug)
            : this(optimization, new PlainStylizer(), new Importer(), debug)
        {
        }

        public Parser(IStylizer stylizer, IImporter importer)
            : this(defaultOptimization, stylizer, importer, defaultDebug)
        {
        }

        public Parser(IStylizer stylizer, IImporter importer, bool debug)
            : this(defaultOptimization, stylizer, importer, debug)
        {
        }

        public Parser(int optimization, IStylizer stylizer, IImporter importer)
            : this(optimization, stylizer, importer, defaultDebug)
        {
        }

        public Parser(int optimization, IStylizer stylizer, IImporter importer, bool debug)
        {
            Stylizer = stylizer;
            Importer = importer;
            Debug = debug;
            Tokenizer = new Tokenizer(optimization);
        }

        public Parser(dotless.Core.configuration.DotlessConfiguration config, IStylizer stylizer, IImporter importer)
            : this(config.Optimization, stylizer, importer, config.Debug)
        {

        }

        public Ruleset Parse(string input, string fileName)
        {
            Ruleset root;
            FileName = fileName;

            try
            {
                Tokenizer.SetupInput(input, fileName);

                var parsers = new Parsers(NodeProvider);
                root = new Root(parsers.Primary(this), GenerateParserError);
            }
            catch (ParsingException e)
            {
                throw GenerateParserError(e);
            }

            if (!Tokenizer.HasCompletedParsing())
                throw GenerateParserError(new ParsingException("Content after finishing parsing (missing opening bracket?)", Tokenizer.GetNodeLocation(Tokenizer.Location.Index)));

            return root;
        }

        private ParserException GenerateParserError(ParsingException parsingException)
        {
            var errorLocation = parsingException.Location;
            var error = parsingException.Message;
            var call = parsingException.CallLocation;

            var zone = new Zone(errorLocation, error, call != null ? new Zone(call) : null);

            var message = Stylizer.Stylize(zone);

            return new ParserException(message, parsingException, zone);
        }
    }
}