/*
 * Copyright 2013-2017 consulo.io
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package consulo.csharp.lang.parser.decl;

import consulo.csharp.lang.parser.CSharpBuilderWrapper;
import consulo.csharp.lang.parser.SharedParsingHelpers;
import consulo.csharp.lang.psi.CSharpSoftTokens;
import consulo.csharp.lang.psi.CSharpTokens;
import com.intellij.lang.PsiBuilder;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.tree.TokenSet;

/**
 * @author VISTALL
 * @since 30.11.13.
 */
public class GenericParameterParsing extends SharedParsingHelpers
{
	private static final TokenSet ourGenericStoppers = TokenSet.create(GT, LBRACE, LPAR, RBRACE);

	public static void parseList(CSharpBuilderWrapper builder)
	{
		if(builder.getTokenType() != LT)
		{
			return;
		}

		PsiBuilder.Marker mark = builder.mark();
		builder.advanceLexer();

		while(!builder.eof())
		{
			parseGenericParameter(builder);

			if(builder.getTokenType() == COMMA)
			{
				builder.advanceLexer();
			}
			else if(ourGenericStoppers.contains(builder.getTokenType()))
			{
				break;
			}
			else if(!ourGenericStoppers.contains(builder.getTokenType()))
			{
				PsiBuilder.Marker errorMarker = builder.mark();
				builder.advanceLexer();
				errorMarker.error("Expected identifier");
			}
		}

		expect(builder, GT, "'>' expected");

		mark.done(GENERIC_PARAMETER_LIST);
	}

	public static void parseGenericParameter(CSharpBuilderWrapper builder)
	{
		PsiBuilder.Marker marker = builder.mark();

		parseModifierListWithAttributes(builder, STUB_SUPPORT);

		if(!expectOrReportIdentifier(builder, STUB_SUPPORT))
		{
			marker.drop();
		}
		else
		{
			marker.done(GENERIC_PARAMETER);
		}
	}

	public static PsiBuilder.Marker parseGenericConstraintList(CSharpBuilderWrapper builder)
	{
		PsiBuilder.Marker marker = builder.mark();

		boolean empty = true;
		while(!builder.eof())
		{
			builder.enableSoftKeyword(CSharpSoftTokens.WHERE_KEYWORD);
			IElementType elementType = builder.getTokenType();
			builder.disableSoftKeyword(CSharpSoftTokens.WHERE_KEYWORD);

			if(elementType == CSharpSoftTokens.WHERE_KEYWORD)
			{
				PsiBuilder.Marker genericConstraint = parseGenericConstraint(builder);
				if(genericConstraint == null)
				{
					break;
				}
				else
				{
					empty = false;
				}
			}
			else
			{
				break;
			}
		}

		if(empty)
		{
			marker.drop();
		}
		else
		{
			marker.done(GENERIC_CONSTRAINT_LIST);
		}
		return marker;
	}

	private static PsiBuilder.Marker parseGenericConstraint(CSharpBuilderWrapper builder)
	{
		if(builder.getTokenType() != WHERE_KEYWORD)
		{
			return null;
		}

		PsiBuilder.Marker marker = builder.mark();

		builder.advanceLexer();

		doneOneElement(builder, CSharpTokens.IDENTIFIER, REFERENCE_EXPRESSION, "Identifier expected");
		if(expect(builder, COLON, "Colon expected"))
		{
			while(!builder.eof())
			{
				PsiBuilder.Marker value = builder.mark();
				IElementType doneElement = null;

				if(builder.getTokenType() == CLASS_KEYWORD || builder.getTokenType() == STRUCT_KEYWORD || builder.getTokenType() == NEW_KEYWORD)
				{
					boolean newKeyword = builder.getTokenType() == NEW_KEYWORD;
					builder.advanceLexer();
					if(newKeyword)
					{
						expect(builder, LPAR, "'(' expected");
						expect(builder, RPAR, "')' expected");
					}

					doneElement = GENERIC_CONSTRAINT_KEYWORD_VALUE;
				}
				else
				{
					if(parseType(builder, STUB_SUPPORT) == null)
					{
						builder.error("Type expected");
					}
					doneElement = GENERIC_CONSTRAINT_TYPE_VALUE;
				}
				value.done(doneElement);

				if(builder.getTokenType() == COMMA)
				{
					builder.advanceLexer();
				}
				else
				{
					break;
				}
			}
		}

		marker.done(GENERIC_CONSTRAINT);
		return marker;
	}
}