// GenPySwift.fu - Python/Swift code generator
//
// Copyright (C) 2020-2025  Piotr Fusik
//
// This file is part of Fusion Transpiler,
// see https://github.com/fusionlanguage/fut
//
// Fusion Transpiler 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.
//
// Fusion Transpiler 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 Fusion Transpiler.  If not, see http://www.gnu.org/licenses/

public abstract class GenPySwift : GenBase
{
	protected override void WriteDocPara!(FuDocPara para, bool many)
	{
		if (many) {
			WriteNewLine();
			StartDocLine();
			WriteNewLine();
			StartDocLine();
		}
		foreach (FuDocInline inline in para.Children) {
			switch (inline) {
			case FuDocText text:
				Write(text.Text);
				break;
			case FuDocCode code:
				WriteChar('`');
				WriteDocCode(code.Text);
				WriteChar('`');
				break;
			case FuDocLine:
				WriteNewLine();
				StartDocLine();
				break;
			default:
				assert false;
			}
		}
	}

	protected abstract string GetDocBullet();

	protected override void WriteDocList!(FuDocList list)
	{
		WriteNewLine();
		foreach (FuDocPara item in list.Items) {
			Write(GetDocBullet());
			WriteDocPara(item, false);
			WriteNewLine();
		}
		StartDocLine();
	}

	protected override void WriteLocalName!(FuSymbol symbol, FuPriority parent)
	{
		if (symbol is FuMember member) {
			if (member.IsStatic())
				WriteName(this.CurrentMethod.Parent);
			else
				Write("self");
			WriteChar('.');
		}
		WriteName(symbol);
	}

	internal override void VisitAggregateInitializer!(FuAggregateInitializer expr)
	{
		Write("[ ");
		WriteCoercedLiterals(expr.Type.AsClassType().GetElementType(), expr.Items);
		Write(" ]");
	}

	internal override void VisitPrefixExpr!(FuPrefixExpr expr, FuPriority parent)
	{
		switch (expr.Op) {
		case FuToken.Increment:
		case FuToken.Decrement:
			expr.Inner.Accept(this, parent);
			break;
		default:
			base.VisitPrefixExpr(expr, parent);
			break;
		}
	}

	internal override void VisitPostfixExpr!(FuPostfixExpr expr, FuPriority parent)
	{
		switch (expr.Op) {
		case FuToken.Increment:
		case FuToken.Decrement:
			expr.Inner.Accept(this, parent);
			break;
		default:
			base.VisitPostfixExpr(expr, parent);
			break;
		}
	}

	static bool IsPtr(FuExpr expr) => expr.Type is FuClassType klass && klass.Class.Id != FuId.StringClass && !(klass is FuStorageType);

	protected abstract string GetReferenceEqOp(bool not);

	protected override void WriteEqual!(FuExpr left, FuExpr right, FuPriority parent, bool not)
	{
		if (IsPtr(left) || IsPtr(right))
			WriteEqualExpr(left, right, parent, GetReferenceEqOp(not));
		else
			base.WriteEqual(left, right, parent, not);
	}

	protected virtual void WriteExpr!(FuExpr expr, FuPriority parent)
	{
		expr.Accept(this, parent);
	}

	protected virtual void WriteElementCoerced!(FuType type, FuExpr value)
	{
		WriteCoerced(type, value, FuPriority.Argument);
	}

	protected void WriteListAppend!(FuExpr obj, List<FuExpr#> args)
	{
		WritePostfix(obj, ".append(");
		FuType elementType = obj.Type.AsClassType().GetElementType();
		if (args.Count == 0)
			WriteNewStorage(elementType);
		else
			WriteElementCoerced(elementType, args[0]);
		WriteChar(')');
	}

	protected virtual bool VisitPreCall!(FuCallExpr call) => false;

	protected bool VisitXcrement!(FuExpr expr, bool postfix, bool write)
	{
		bool seen;
		switch (expr) {
		case FuVar def:
			return def.Value != null && VisitXcrement(def.Value, postfix, write);
		case FuAggregateInitializer:
		case FuLiteral:
		case FuLambdaExpr:
			return false;
		case FuInterpolatedString interp:
			seen = false;
			foreach (FuInterpolatedPart part in interp.Parts)
				seen |= VisitXcrement(part.Argument, postfix, write);
			return seen;
		case FuSymbolReference symbol:
			return symbol.Left != null && VisitXcrement(symbol.Left, postfix, write);
		case FuUnaryExpr unary:
			if (unary.Inner == null) // new C()
				return false;
			seen = VisitXcrement(unary.Inner, postfix, write);
			if ((unary.Op == FuToken.Increment || unary.Op == FuToken.Decrement) && postfix == unary is FuPostfixExpr) {
				if (write) {
					WriteExpr(unary.Inner, FuPriority.Assign);
					WriteLine(unary.Op == FuToken.Increment ? " += 1" : " -= 1");
				}
				seen = true;
			}
			return seen;
		case FuBinaryExpr binary:
			seen = VisitXcrement(binary.Left, postfix, write);
			if (binary.Op == FuToken.Is)
				return seen;
			if (binary.Op == FuToken.CondAnd || binary.Op == FuToken.CondOr)
				assert !VisitXcrement(binary.Right, postfix, false);
			else
				seen |= VisitXcrement(binary.Right, postfix, write);
			return seen;
		case FuSelectExpr select:
			seen = VisitXcrement(select.Cond, postfix, write);
			assert !VisitXcrement(select.OnTrue, postfix, false);
			assert !VisitXcrement(select.OnFalse, postfix, false);
			return seen;
		case FuCallExpr call:
			seen = VisitXcrement(call.Method, postfix, write);
			foreach (FuExpr arg in call.Arguments)
				seen |= VisitXcrement(arg, postfix, write);
			if (!postfix)
				seen |= VisitPreCall(call);
			return seen;
		default:
			assert false;
		}
	}

	internal override void VisitExpr!(FuExpr statement)
	{
		VisitXcrement(statement, false, true);
		if (!(statement is FuUnaryExpr unary) || (unary.Op != FuToken.Increment && unary.Op != FuToken.Decrement)) {
			WriteExpr(statement, FuPriority.Statement);
			WriteNewLine();
			if (statement is FuVar def)
				WriteInitCode(def);
		}
		VisitXcrement(statement, true, true);
		CleanupTemporaries();
	}

	protected override void EndStatement!()
	{
		WriteNewLine();
	}

	protected abstract void OpenChild!();

	protected abstract void CloseChild!();

	protected override void WriteChild!(FuStatement! statement)
	{
		OpenChild();
		statement.AcceptStatement(this);
		CloseChild();
	}

	internal override void VisitBlock!(FuBlock statement)
	{
		WriteStatements(statement.Statements);
	}

	bool OpenCond!(string statement, FuExpr cond, FuPriority parent)
	{
		VisitXcrement(cond, false, true);
		Write(statement);
		WriteExpr(cond, parent);
		OpenChild();
		return VisitXcrement(cond, true, true);
	}

	protected virtual void WriteContinueDoWhile!(FuExpr cond)
	{
		OpenCond("if ", cond, FuPriority.Argument);
		WriteLine("continue");
		CloseChild();
		VisitXcrement(cond, true, true);
		WriteLine("break");
	}

	protected virtual bool NeedCondXcrement!(FuLoop loop) => loop.Cond != null;

	void EndBody!(FuLoop loop)
	{
		if (loop is FuFor forLoop) {
			if (forLoop.IsRange)
				return;
			VisitOptionalStatement(forLoop.Advance);
		}
		if (NeedCondXcrement(loop))
			VisitXcrement(loop.Cond, false, true);
	}

	internal override void VisitContinue!(FuContinue statement)
	{
		if (statement.Loop is FuDoWhile doWhile)
			WriteContinueDoWhile(doWhile.Cond);
		else {
			EndBody(statement.Loop);
			WriteLine("continue");
		}
	}

	void OpenWhileTrue!()
	{
		Write("while ");
		VisitLiteralTrue();
		OpenChild();
	}

	protected abstract string GetIfNot();

	internal override void VisitDoWhile!(FuDoWhile statement)
	{
		OpenWhileTrue();
		statement.Body.AcceptStatement(this);
		if (statement.Body.CompletesNormally()) {
			OpenCond(GetIfNot(), statement.Cond, FuPriority.Primary);
			WriteLine("break");
			CloseChild();
			VisitXcrement(statement.Cond, true, true);
		}
		CloseChild();
	}

	protected virtual void OpenWhile!(FuLoop loop)
	{
		OpenCond("while ", loop.Cond, FuPriority.Argument);
	}

	void CloseWhile!(FuLoop loop)
	{
		loop.Body.AcceptStatement(this);
		if (loop.Body.CompletesNormally())
			EndBody(loop);
		CloseChild();
		if (NeedCondXcrement(loop)) {
			if (loop.HasBreak && VisitXcrement(loop.Cond, true, false)) {
				Write("else");
				OpenChild();
				VisitXcrement(loop.Cond, true, true);
				CloseChild();
			}
			else
				VisitXcrement(loop.Cond, true, true);
		}
	}

	protected abstract void WriteForRange!(FuVar iter, FuBinaryExpr cond, long rangeStep);

	internal override void VisitFor!(FuFor statement)
	{
		if (statement.IsRange) {
			assert statement.Init is FuVar indVar;
			Write("for ");
			if (statement.IsIndVarUsed)
				WriteName(indVar);
			else
				WriteChar('_');
			Write(" in ");
			assert statement.Cond is FuBinaryExpr cond;
			WriteForRange(indVar, cond, statement.RangeStep);
			WriteChild(statement.Body);
		}
		else {
			VisitOptionalStatement(statement.Init);
			if (statement.Cond != null)
				OpenWhile(statement);
			else
				OpenWhileTrue();
			CloseWhile(statement);
		}
	}

	protected abstract void WriteElseIf!();

	internal override void VisitIf!(FuIf statement)
	{
		if (TryWriteIfTryParse(statement))
			return;
		bool condPostXcrement = OpenCond("if ", statement.Cond, FuPriority.Argument);
		statement.OnTrue.AcceptStatement(this);
		CloseChild();
		if (statement.OnFalse == null && condPostXcrement && !statement.OnTrue.CompletesNormally())
			VisitXcrement(statement.Cond, true, true);
		else if (statement.OnFalse != null || condPostXcrement) {
			if (!condPostXcrement && statement.OnFalse is FuIf! childIf && !VisitXcrement(childIf.Cond, false, false)) {
				WriteElseIf();
				VisitIf(childIf);
			}
			else {
				Write("else");
				OpenChild();
				VisitXcrement(statement.Cond, true, true);
				VisitOptionalStatement(statement.OnFalse);
				CloseChild();
			}
		}
	}

	protected abstract void WriteResultVar!();

	internal override void VisitReturn!(FuReturn statement)
	{
		if (statement.Value == null)
			WriteLine("return");
		else {
			VisitXcrement(statement.Value, false, true);
			WriteTemporaries(statement.Value);
			if (VisitXcrement(statement.Value, true, false)) {
				WriteResultVar(); // FIXME: name clash? only matters if return ... result++, unlikely
				Write(" = ");
				WriteCoercedExpr(this.CurrentMethod.Type, statement.Value);
				WriteNewLine();
				VisitXcrement(statement.Value, true, true);
				WriteLine("return result");
			}
			else {
				Write("return ");
				WriteCoercedExpr(this.CurrentMethod.Type, statement.Value);
				WriteNewLine();
			}
			CleanupTemporaries();
		}
	}

	internal override void VisitWhile!(FuWhile statement)
	{
		OpenWhile(statement);
		CloseWhile(statement);
	}
}