class TextPatternParser {

    public static TextPattern parse( String pattern )
        throws TextPatternParsingException
    {
        List<TextPatternSegment> segments = new ArrayList<>();

        // True if we just parsed a Segment, False if we parsed a join or haven't parsed anything.
        boolean segment = false;

        boolean invalidExpression = true;

        Matcher m;

        if ( pattern != null && !pattern.isEmpty() )
        {
            m = EXPRESSION_REGEX.matcher( pattern );
        }
        else
        {
            throw new TextPatternParsingException( "Supplied expression was null or empty.", -1 );
        }

        /*
         * We go trough all matches. Matches can be one of the following:
         * 
         * <ul>
         *   <li>a TEXT method ("..")</li>
         *   <li>any TextPatternMethod (Excluding TEXT) (method(param))</li>
         *   <li>a join ( + )</li>
         * </ul>
         *
         * Matches that are invalid includes methods with unknown method names
         */
        while ( m.find() )
        {
            invalidExpression = false;

            // This returns the entire method syntax, including params
            String method = m.group( "Method" );

            // This means we found a match for method syntax
            if ( method != null )
            {

                // This returns only the name of the method (see TextPatternMethod for valid names)
                String methodName = m.group( "MethodName" );

                // This means we encountered the syntax for TEXT method
                if ( methodName == null ) // Text
                {

                    // Only add if valid syntax, else it will throw exception after if-else.
                    if ( TextPatternMethod.TEXT.getType().validatePattern( method ) )
                    {
                        segment = true;
                        segments.add( new TextPatternSegment( TextPatternMethod.TEXT, method ) );
                        continue;
                    }

                }

                // Catch all other methods
                else
                {
                    // Attempt to find a matching method name in TextPatternMethod
                    try
                    {
                        TextPatternMethod textPatternMethod = TextPatternMethod.valueOf( methodName );

                        // Only add if valid syntax, else it will throw exception after if-else.
                        if ( textPatternMethod.getType().validatePattern( method ) )
                        {
                            segment = true;
                            segments.add( new TextPatternSegment( textPatternMethod, method ) );
                            continue;
                        }
                    }
                    catch ( Exception e )
                    {
                        // Ignore, throw exception after if-else if we get here.
                    }
                }

                // If we are here, that means we found no matching methods, so throw an exception
                throw new TextPatternParsingException( "Failed to parse the following method: '" + method + "'", m.start( "Method" ) );
            }

            // Handle Join
            else if ( m.group( "Join" ) != null )
            {
                // Join should only be after a Segment
                if ( !segment )
                {
                    throw new TextPatternParsingException( "Unexpected '+'", m.start( "JoinValue" ) );
                }
                else
                {
                    segment = false;
                }
            }
        }

        // If the matcher had no matches
        if ( invalidExpression )
        {
            throw new TextPatternParsingException( "The expression is invalid", -1 );
        }

        // An expression should not end on a Join
        if ( !segment )
        {
            throw new TextPatternParsingException( "Unexpected '+' at the end of the expression", -1 );
        }

        return new TextPattern( segments );
    }

}