//
//  ParameterDefinitonParser.h
//  snowcrash
//
//  Created by Zdenek Nemec on 9/1/13.
//  Copyright (c) 2013 Apiary Inc. All rights reserved.
//

#ifndef SNOWCRASH_PARAMETERDEFINITIONPARSER_H
#define SNOWCRASH_PARAMETERDEFINITIONPARSER_H

#include <sstream>
#include "BlueprintParserCore.h"
#include "Blueprint.h"
#include "RegexMatch.h"
#include "StringUtility.h"
#include "ListBlockUtility.h"
#include "SectionUtility.h"
#include "DescriptionSectionUtility.h"

/** Parameter Value regex */
#define PARAMETER_VALUE "`([^`]+)`"

/** Parameter Identifier */
#define PARAMETER_IDENTIFIER "(([[:alnum:]_.-])*|(%[A-Fa-f0-9]{2})*)+"

/** Lead in and out for comma separated values regex */
#define CSV_LEADINOUT "[[:blank:]]*,?[[:blank:]]*"

namespace snowcrashconst {

    /** Parameter Required matching regex */
    const char* const ParameterRequiredRegex = "^[[:blank:]]*[Rr]equired[[:blank:]]*$";

    /** Parameter Optional matching regex */
    const char* const ParameterOptionalRegex = "^[[:blank:]]*[Oo]ptional[[:blank:]]*$";

    /** Additonal Parameter Traits Example matching regex */
    const char* const AdditionalTraitsExampleRegex = CSV_LEADINOUT "`([^`]*)`" CSV_LEADINOUT;

    /** Additonal Parameter Traits Use matching regex */
    const char* const AdditionalTraitsUseRegex = CSV_LEADINOUT "([Oo]ptional|[Rr]equired)" CSV_LEADINOUT;

    /** Additonal Parameter Traits Type matching regex */
    const char* const AdditionalTraitsTypeRegex = CSV_LEADINOUT "([^,]*)" CSV_LEADINOUT;

    /** Parameter Values matching regex */
    const char* const ParameterValuesRegex = "^[[:blank:]]*[Vv]alues[[:blank:]]*$";

    /** Values expected content */
    const char* const ExpectedValuesContent = "nested list of possible parameter values, one element per list item e.g. '`value`'";

    const std::string DescriptionIdentifier = "...";
}

namespace snowcrash {
    
    /**
     *  Classifier of internal list items, ParameterCollection context.
     */
    template <>
    FORCEINLINE SectionType ClassifyInternaListBlock<Parameter>(const BlockIterator& begin,
                                                            const BlockIterator& end) {


        if (begin->type != ListBlockBeginType &&
            begin->type != ListItemBlockBeginType)
            return UndefinedSectionType;
        
        SourceData remainingContent;
        SourceData content = GetListItemSignature(begin, end, remainingContent);
        
        content = TrimString(content);
        
        if (RegexMatch(content, snowcrashconst::ParameterValuesRegex))
            return ParameterValuesSectionType;
        
        return UndefinedSectionType;
    }
    
    /** Children blocks classifier */
    template <>
    FORCEINLINE SectionType ClassifyChildrenListBlock<Parameter>(const BlockIterator& begin,
                                                                 const BlockIterator& end) {
        
        SectionType type = ClassifyInternaListBlock<Parameter>(begin, end);
        if (type != UndefinedSectionType)
            return type;
                
        return UndefinedSectionType;
    }
   
    /** Determine if a signature is a valid parameter*/
    FORCEINLINE bool IsValidParameterSignature(const SourceData& signature) {

        SourceData innerSignature = signature;
        innerSignature = TrimString(innerSignature);

        if (innerSignature.length() == 0) {
            return false; // Empty string, invalid
        }

        size_t firstSpace = innerSignature.find(" ");
        if (firstSpace == std::string::npos) {
            return RegexMatch(innerSignature, "^" PARAMETER_IDENTIFIER "$");
        }
        
        std::string paramName = innerSignature.substr(0, firstSpace);
        if (!RegexMatch(paramName, "^" PARAMETER_IDENTIFIER "$")) {
            return false; // Invalid param name
        }
        // Remove param name
        innerSignature = innerSignature.substr(firstSpace + 1);

        size_t descriptionPos = innerSignature.find(snowcrashconst::DescriptionIdentifier);
        // Remove description
        if (descriptionPos != std::string::npos) {
            innerSignature = innerSignature.substr(0, descriptionPos);
            innerSignature = TrimString(innerSignature);
        }

        size_t attributesPos = innerSignature.find("(");
        if (attributesPos != std::string::npos) {
            size_t endOfAttributesPos = innerSignature.find_last_of(")");
            if (endOfAttributesPos == std::string::npos) {
                return false; // Expecting close of attributes
            }
            // Remove attributes
            innerSignature = innerSignature.substr(0, attributesPos);
            innerSignature = TrimString(innerSignature);
        }

        if (innerSignature.length() == 0) {
            return true;
        }

        if (innerSignature.substr(0,1) == "=") {
            innerSignature = innerSignature.substr(1);
            innerSignature = TrimString(innerSignature);
            if (innerSignature.length() == 0) {
                return false; // No default value
            }
            if (innerSignature.substr(0,1) == "`" && innerSignature.substr(innerSignature.length()-1,1) == "`") {
                return true;
            }
        }

        return false;
    }

    /**
     *  Returns true if given block has a parameter definition signature, false otherwise.
     */
    FORCEINLINE bool HasParameterDefinitionSignature(const BlockIterator& begin,
                                                     const BlockIterator& end) {
        
        if (begin->type != ListBlockBeginType &&
            begin->type != ListItemBlockBeginType)
            return false;
        
        // Since we are too generic make sure the signature is not inner list
        SectionType listSection = ClassifyInternaListBlock<Parameter>(begin, end);
        if (listSection != UndefinedSectionType)
            return false;
        
        // Or any other reserved keyword
        if (HasParametersSignature(begin, end))
            return false;
        
        SourceData remainingContent;
        SourceData content = GetListItemSignature(begin, end, remainingContent);
        content = TrimString(content);
        return IsValidParameterSignature(content);
    }

    /**
     *  Block Classifier, Parameter context.
     */
    template <>
    FORCEINLINE SectionType ClassifyBlock<Parameter>(const BlockIterator& begin,
                                                 const BlockIterator& end,
                                                 const SectionType& context) {
        
        if (context == UndefinedSectionType) {
            if (HasParameterDefinitionSignature(begin, end))
                return ParameterDefinitionSectionType;
        }
        else if (context == ParameterDefinitionSectionType) {

            if (begin->type == ListItemBlockEndType ||
                begin->type == ListBlockEndType)
                return UndefinedSectionType;
            
            SectionType listSection = ClassifyInternaListBlock<Parameter>(begin, end);
            if (listSection != UndefinedSectionType)
                return listSection;
            
            if (begin->type == ListBlockBeginType)
                return ForeignSectionType; // Foreign nested list-item
            
            if (begin->type == ListItemBlockBeginType)
                return UndefinedSectionType;
        }
        else if (context == ParameterValuesSectionType ||
                 context == ForeignSectionType) {

            if (begin->type == ListItemBlockEndType ||
                begin->type == ListBlockEndType)
                return UndefinedSectionType;
            
            SectionType listSection = ClassifyInternaListBlock<Parameter>(begin, end);
            if (listSection != UndefinedSectionType)
                return listSection;
            
            return ForeignSectionType;
        }

        return (context == ParameterDefinitionSectionType) ? context : UndefinedSectionType;
    }
    
    /**
     *  Parameter section parser.
     */
    template<>
    struct SectionParser<Parameter> {
        
        static ParseSectionResult ParseSection(const BlueprintSection& section,
                                               const BlockIterator& cur,
                                               BlueprintParserCore& parser,
                                               Parameter& parameter) {
            
            ParseSectionResult result = std::make_pair(Result(), cur);
            switch (section.type) {
                    
                case ParameterDefinitionSectionType:
                    result = HandleParmeterDefinitionSection(section, cur, parser, parameter);
                    break;
                    
                case ParameterValuesSectionType:
                    result = HandleValuesSection(section, cur, parser, parameter);
                    break;
                    
                case ForeignSectionType:
                    result = HandleForeignSection<Parameter>(section, cur, parser.sourceData);
                    break;
                    
                case UndefinedSectionType:
                    result.second = CloseList(cur, section.bounds.second);
                    break;
                    
                default:
                    result.first.error = UnexpectedBlockError(section, cur, parser.sourceData);
                    break;
            }
            
            return result;
        }
        
        
        static void Finalize(const SectionBounds& bounds,
                             BlueprintParserCore& parser,
                             Parameter& parameter,
                             Result& result) {}
        
        /** Parse a parameter definition top-level section blocks. */
        static ParseSectionResult HandleParmeterDefinitionSection(const BlueprintSection& section,
                                                                  const BlockIterator& cur,
                                                                  BlueprintParserCore& parser,
                                                                  Parameter& parameter) {
            
            ParseSectionResult result = std::make_pair(Result(), cur);
            BlockIterator sectionCur = cur;

            // Signature
            if (sectionCur == section.bounds.first) {
                ProcessSignature(section, sectionCur, parser.sourceData, result.first, parameter);
                result.second = SkipSignatureBlock(sectionCur, section.bounds.second);
                return result;
            }

            // Description
            result = ParseDescriptionBlock<Parameter>(section,
                                                       sectionCur,
                                                       parser.sourceData,
                                                       parameter);
            return result;
            
        }

      
        
        /**
         *  Retrieve and process parameter definition signature.
         */
        static void ProcessSignature(const BlueprintSection& section,
                                     const BlockIterator& cur,
                                     const SourceData& sourceData,
                                     Result& result,
                                     Parameter& parameter) {
            
            // Set default values
            parameter.use = UndefinedParameterUse;
            
            // Process signature
            SourceData remainingContent;
            SourceData signature = GetListItemSignature(cur, section.bounds.second, remainingContent);
            
            TrimString(signature);

            if (IsValidParameterSignature(signature)) {
                
                SourceData innerSignature = signature;
                innerSignature = TrimString(innerSignature);

                size_t firstSpace = innerSignature.find(" ");
                if (firstSpace == std::string::npos) {
                    // Name
                    parameter.name = signature;
                }
                else {
                    parameter.name = innerSignature.substr(0, firstSpace);
                    innerSignature = innerSignature.substr(firstSpace + 1);
                    size_t descriptionPos = innerSignature.find(snowcrashconst::DescriptionIdentifier);
                    if (descriptionPos != std::string::npos) {
                        // Description
                        parameter.description = innerSignature.substr(descriptionPos);
                        parameter.description = TrimString(parameter.description.replace(0, snowcrashconst::DescriptionIdentifier.length(), ""));
                        innerSignature = innerSignature.substr(0, descriptionPos);
                        innerSignature = TrimString(innerSignature);
                    }

                    size_t attributesPos = innerSignature.find("(");
                    if (attributesPos != std::string::npos) {
                        size_t endOfAttributesPos = innerSignature.find_last_of(")");
                        if (endOfAttributesPos - attributesPos > 1) {
                            std::string attributes = innerSignature.substr(attributesPos, endOfAttributesPos - attributesPos);
                            attributes = attributes.substr(1);
                            ProcessSignatureAdditionalTraits(section, cur, attributes, sourceData, result, parameter);
                            innerSignature = innerSignature.substr(0, attributesPos);
                            innerSignature = TrimString(innerSignature);
                        }
                    }

                    if (innerSignature.length() > 0) {
                        // Remove =
                        parameter.defaultValue = innerSignature;
                        parameter.defaultValue.erase(std::remove(parameter.defaultValue.begin(), parameter.defaultValue.end(), '='), parameter.defaultValue.end());
                        parameter.defaultValue.erase(std::remove(parameter.defaultValue.begin(), parameter.defaultValue.end(), '`'), parameter.defaultValue.end());
                        parameter.defaultValue = TrimString(parameter.defaultValue);
                    }
                }
                // Check possible required vs default clash
                if (parameter.use != OptionalParameterUse &&
                    !parameter.defaultValue.empty()) {
                    
                    // WARN: Required vs default clash
                    std::stringstream ss;
                    ss << "specifying parameter '" << parameter.name << "' as required supersedes its default value"\
                          ", declare the parameter as 'optional' to specify its default value";
                    
                    BlockIterator nameBlock = ListItemNameBlock(cur, section.bounds.second);
                    SourceCharactersBlock sourceBlock = CharacterMapForBlock(nameBlock, cur, section.bounds, sourceData);
                    result.warnings.push_back(Warning(ss.str(),
                                                      LogicalErrorWarning,
                                                      sourceBlock));
                }
            }
            else {
                // ERR: unable to parse 
                BlockIterator nameBlock = ListItemNameBlock(cur, section.bounds.second);
                SourceCharactersBlock sourceBlock = CharacterMapForBlock(nameBlock, cur, section.bounds, sourceData);
                result.error = (Error("unable to parse parameter specification",
                                      BusinessError,
                                      sourceBlock));
            }
        }

        
        /** Parse additional parameter attributes from abbrev definition bracket */
        static void ProcessSignatureAdditionalTraits(const BlueprintSection& section,
                                                     const BlockIterator& cur,
                                                     const SourceData& additionalTraits,
                                                     const SourceData& sourceData,
                                                     Result& result,
                                                     Parameter& parameter)
        {
            
            // Cherry pick example value, if any
            std::string source = additionalTraits;
            TrimString(source);
            CaptureGroups captureGroups;
            if (RegexCapture(source, snowcrashconst::AdditionalTraitsExampleRegex, captureGroups) &&
                captureGroups.size() > 1) {
                
                parameter.exampleValue = captureGroups[1];
                std::string::size_type pos = source.find(captureGroups[0]);
                if (pos != std::string::npos)
                    source.replace(pos, captureGroups[0].length(), std::string());
            }
            
            // Cherry pick use attribute, if any
            captureGroups.clear();
            if (RegexCapture(source, snowcrashconst::AdditionalTraitsUseRegex, captureGroups) &&
                captureGroups.size() > 1) {
                
                parameter.use = (RegexMatch(captureGroups[1], snowcrashconst::ParameterOptionalRegex)) ? OptionalParameterUse : RequiredParameterUse;

                std::string::size_type pos = source.find(captureGroups[0]);
                if (pos != std::string::npos)
                    source.replace(pos, captureGroups[0].length(), std::string());
            }
            
            // Finish with type
            captureGroups.clear();
            if (RegexCapture(source, snowcrashconst::AdditionalTraitsTypeRegex, captureGroups) &&
                captureGroups.size() > 1) {
                
                parameter.type = captureGroups[1];
                
                std::string::size_type pos = source.find(captureGroups[0]);
                if (pos != std::string::npos)
                    source.replace(pos, captureGroups[0].length(), std::string());
            }
            
            // Check whats left
            TrimString(source);
            if (!source.empty()) {
                // WARN: Additional parameters traits warning
                std::stringstream ss;
                ss << "unable to parse additional parameter traits";
                ss << ", expected '([required | optional], [<type>], [`<example value>`])'";
                ss << ", e.g. '(optional, string, `Hello World`)'";

                BlockIterator nameBlock = ListItemNameBlock(cur, section.bounds.second);
                SourceCharactersBlock sourceBlock = CharacterMapForBlock(nameBlock, cur, section.bounds, sourceData);
                result.warnings.push_back(Warning(ss.str(),
                                                  FormattingWarning,
                                                  sourceBlock));
                
                parameter.type.clear();
                parameter.exampleValue.clear();
                parameter.use = UndefinedParameterUse;
            }
        }
        
        /** Parse possible values enumeration section blocks. */
        static ParseSectionResult HandleValuesSection(const BlueprintSection& section,
                                                      const BlockIterator& cur,
                                                      BlueprintParserCore& parser,
                                                      Parameter& parameter) {
            
            ParseSectionResult result = std::make_pair(Result(), cur);
            
            // Check redefinition
            if (!parameter.values.empty()) {
                // WARN: parameter values are already defined
                std::stringstream ss;
                ss << "overshadowing previous 'values' definition";
                ss << " for parameter '" << parameter.name << "'";
                
                BlockIterator nameBlock = ListItemNameBlock(cur, section.bounds.second);
                SourceCharactersBlock sourceBlock = CharacterMapForBlock(nameBlock, cur, section.bounds, parser.sourceData);
                result.first.warnings.push_back(Warning(ss.str(),
                                                        RedefinitionWarning,
                                                        sourceBlock));
            }
            
            // Clear any previous content
            parameter.values.clear();
            
            // Check additional content in signature
            CheckSignatureAdditionalContent(section,
                                            cur,
                                            parser.sourceData,
                                            "'values:' keyword",
                                            snowcrashconst::ExpectedValuesContent,
                                            result.first);

            // Parse inner list of entities
            BlockIterator sectionCur = SkipSignatureBlock(cur, section.bounds.second);
            BlockIterator endCur = cur;
            if (endCur->type == ListBlockBeginType)
                ++endCur;
            endCur = SkipToClosingBlock(endCur, section.bounds.second, ListItemBlockBeginType, ListItemBlockEndType);
            
            if (sectionCur != endCur) {

                // Iterate over list blocks, try to parse any nested lists of possible elements
                for (; sectionCur != endCur; ++sectionCur) {
                    
                    if (sectionCur->type == QuoteBlockBeginType)
                        sectionCur = SkipToClosingBlock(sectionCur, endCur, QuoteBlockBeginType, QuoteBlockEndType);
                    
                    bool entitiesParsed = false;
                    if (sectionCur->type == ListBlockBeginType) {
                        if (parameter.values.empty()) {
                            
                            // Try to parse some values
                            ParseSectionResult valuesResult = ParseValuesEntities(sectionCur,
                                                                                  section.bounds,
                                                                                  parser,
                                                                                  parameter.values);
                            result.first += valuesResult.first;
                            sectionCur = valuesResult.second;
                            if (result.first.error.code != Error::OK)
                                return result;

                            entitiesParsed = true;
                        }
                        else {
                            sectionCur = SkipToClosingBlock(sectionCur, endCur, ListBlockBeginType, ListBlockEndType);
                        }
                    }
                    
                    if (!entitiesParsed) {
                        // WARN: ignoring extraneous content
                        std::stringstream ss;
                        ss << "ignoring additional content in the 'values' attribute of the '";
                        ss << parameter.name << "' parameter";
                        ss << ", " << snowcrashconst::ExpectedValuesContent;
                        
                        SourceCharactersBlock sourceBlock = CharacterMapForBlock(sectionCur, cur, section.bounds, parser.sourceData);
                        result.first.warnings.push_back(Warning(ss.str(),
                                                                IgnoringWarning,
                                                                sourceBlock));
                    }
                }
            }
            
            if (parameter.values.empty()) {
                // WARN: empty definition
                std::stringstream ss;
                ss << "no possible values specified for parameter '" << parameter.name << "'";
                SourceCharactersBlock sourceBlock = CharacterMapForBlock(sectionCur, cur, section.bounds, parser.sourceData);
                result.first.warnings.push_back(Warning(ss.str(),
                                                        EmptyDefinitionWarning,
                                                        sourceBlock));
            }

            if((!parameter.exampleValue.empty() || !parameter.defaultValue.empty()) && !parameter.values.empty()) {
                CheckExampleAndDefaultValue(section, sectionCur, parser, parameter, result);
            }
            
            endCur = CloseList(sectionCur, section.bounds.second);
            result.second = endCur;
            return result;
        }
        
        /** Parse entities in values attribute */
        static ParseSectionResult ParseValuesEntities(const BlockIterator& cur,
                                                      const SectionBounds& bounds,
                                                      BlueprintParserCore& parser,
                                                      Collection<Value>::type& values) {
         
            ParseSectionResult result = std::make_pair(Result(), cur);
            
            if (cur->type != ListBlockBeginType)
                return result;
            
            BlockIterator sectionCur = ContentBlock(cur, bounds.second);
            
            while (sectionCur != bounds.second &&
                   sectionCur->type == ListItemBlockBeginType) {
                
                sectionCur = SkipToClosingBlock(sectionCur, bounds.second, ListItemBlockBeginType, ListItemBlockEndType);
                
                CaptureGroups captureGroups;
                std::string content = sectionCur->content;
                if (content.empty()) {
                    // Not inline list, map from source
                    content = MapSourceData(parser.sourceData, sectionCur->sourceMap);
                }

                RegexCapture(content, PARAMETER_VALUE, captureGroups);
                if (captureGroups.size() > 1) {
                    values.push_back(captureGroups[1]);
                }
                else {
                    // WARN: Ignoring unexpected content
                    TrimString(content);
                    std::stringstream ss;
                    ss << "ignoring the '" << content << "' element";
                    ss << ", expected '`" << content << "`'";
                    
                    SourceCharactersBlock sourceBlock = CharacterMapForBlock(sectionCur, cur, bounds, parser.sourceData);
                    result.first.warnings.push_back(Warning(ss.str(),
                                                            IgnoringWarning,
                                                            sourceBlock));
                }
                
                ++sectionCur;
            }
            
            result.second = sectionCur;
            return result;
        }
        
        static void CheckExampleAndDefaultValue(const BlueprintSection& section,
                                                const BlockIterator& cur,
                                                const BlueprintParserCore& parser,
                                                const Parameter& parameter,
                                                ParseSectionResult& result) {

            bool isExampleFound = false;
            bool isDefaultFound = false;

            for (Collection<Value>::const_iterator it = parameter.values.begin(); it != parameter.values.end(); ++it){
                if(parameter.exampleValue == *it) {
                    isExampleFound = true;
                }
                if(parameter.defaultValue == *it) {
                    isDefaultFound = true;
                }
            }

            if(!parameter.exampleValue.empty() && !isExampleFound) {
                // WARN: missing example in values.
                std::stringstream ss;
                ss << "the example value '" << parameter.exampleValue << "' of parameter '"<< parameter.name <<"' is not in its list of expected values";
                SourceCharactersBlock sourceBlock = CharacterMapForBlock(cur, section.bounds.second, section.bounds, parser.sourceData);
                result.first.warnings.push_back(Warning(ss.str(),
                                                LogicalErrorWarning,
                                                sourceBlock));
            }

            if(!parameter.defaultValue.empty() && !isDefaultFound) {
                // WARN: missing default in values.
                std::stringstream ss;
                ss << "the default value '" << parameter.defaultValue << "' of parameter '"<< parameter.name <<"' is not in its list of expected values";
                SourceCharactersBlock sourceBlock = CharacterMapForBlock(cur, section.bounds.second, section.bounds, parser.sourceData);
                result.first.warnings.push_back(Warning(ss.str(),
                                                LogicalErrorWarning,
                                                sourceBlock));
            }

            return;
        }


    };
    
    typedef BlockParser<Parameter, SectionParser<Parameter> > ParameterDefinitionParser;
}

#endif