<?php

namespace MediaWiki\Extension\Math\Tests\WikiTexVC\Nodes;

use InvalidArgumentException;
use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLbase;
use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmo;
use MediaWiki\Extension\Math\WikiTexVC\Nodes\DQ;
use MediaWiki\Extension\Math\WikiTexVC\Nodes\Fun1nb;
use MediaWiki\Extension\Math\WikiTexVC\Nodes\Literal;
use MediaWiki\Extension\Math\WikiTexVC\Nodes\TexArray;
use MediaWiki\Extension\Math\WikiTexVC\Nodes\TexNode;
use MediaWikiIntegrationTestCase;

/**
 * @covers \MediaWiki\Extension\Math\WikiTexVC\Nodes\TexArray
 */
class TexArrayTest extends MediaWikiIntegrationTestCase {

	public function testIsTexNode() {
		$this->assertTrue( new TexArray() instanceof TexNode,
			'Should create an instance of TexNode' );
	}

	public function testConcatOutput() {
		$ta = new TexArray( new Literal( 'a' ), new Literal( 'b' ) );
		$this->assertEquals( 'ab', $ta->render(), 'Should concatenate its input' );
	}

	public function testExtractCurlies() {
		$n = new TexNode( new TexArray( new Literal( 'a' ) ) );
		$this->assertEquals( '{a}', $n->inCurlies(),
			'Should create exactly one pair of curlies' );
	}

	public function testExtractIdentifiers() {
		$n = new TexArray( new Literal( 'd' ) );
		$this->assertEquals( [ 'd' ], $n->extractIdentifiers(),
			'Should extract identifiers' );
	}

	public function testExtractIdentifiersFromArg() {
		$n = new TexArray();
		$this->assertEquals( [ 'd' ],
			$n->extractIdentifiers( [ new Literal( 'd' ) ] ),
			'Should extract identifiers from the argument' );
	}

	public function testExtractSplitIdentifiers() {
		$n = new TexArray( new Literal( 'a' ), new Literal( '\'' ) );
		$this->assertEquals( [ 'a\'' ], $n->extractIdentifiers(),
			'Should extract split identifiers' );
	}

	public function testNotConfuseIntegralsIdentifiers() {
		$n = new TexArray( new Literal( 'd' ), new Literal( '\\int' ) );
		$this->assertEquals( [ 'd' ], $n->extractIdentifiers(),
			'Should not confuse integrals and identifiers' );
	}

	public function testNotConfuseIntegralD() {
		$n = new TexArray( new Literal( '\\int' ), new Literal( 'd' ) );
		$this->assertEquals( [], $n->extractIdentifiers(), 'Should not confuse integral d with d identifier' );
	}

	public function testNotConfuseUprightIntegralD() {
		$n = new TexArray( new Literal( '\\int' ), new Literal( '\\mathrm{d}' ) );
		$this->assertEquals( [], $n->extractIdentifiers(),
			'Should not confuse upright integral d with d identifier' );
	}

	public function testExtractIdentifierMods() {
		$n = new TexArray( new TexNode( '' ) );
		$this->assertEquals( [], $n->getModIdent(), 'Should extract identifier modifications' );
	}

	public function testExtractSubscripts() {
		$n = new TexArray( new TexNode( '' ) );
		$this->assertEquals( [], $n->extractSubscripts(), 'Should extract subscripts' );
	}

	public function testAddNotAppendsOverlayToMMLmoWhenNotFlagIsSet() {
		$state = [ 'not' => true ];
		$mmlmo = new MMLmo( "", [], "∈" );

		$ta = new TexArray();
		$result = $ta->addNot( $state, $mmlmo );

		$this->assertInstanceOf( MMLmo::class, $result );
		$this->assertEquals(
			"∈&#x338;",
			$result->getText(),
			'Should append "not" overlay (U+0338) to MMLmo text'
		);
	}

	public function testAddNotDoesNothingWhenNotFlagIsNotSet() {
		$state = [];
		$mmlmo = new MMLmo( "", [], "∈" );

		$ta = new TexArray();
		$result = $ta->addNot( $state, $mmlmo );

		$this->assertInstanceOf( MMLmo::class, $result );
		$this->assertEquals(
			"∈",
			$result->getText(),
			'Should not modify text for unset "not" flag'
		);
	}

	public function testAddNotDoesNotProcessNonMMLmoObjects() {
		$state = [ 'not' => true ];
		$nonMMLmo = new MMLbase( 'dummy' );

		$ta = new TexArray();
		$result = $ta->addNot( $state, $nonMMLmo );

		$this->assertEquals( $nonMMLmo, $result, 'Should not modify non-MMLmo objects' );
	}

	public function testAddNotDoesNotProcessStringInput() {
		$state = [ 'not' => true ];
		$input = '∈';

		$ta = new TexArray();
		$result = $ta->addNot( $state, $input );

		$this->assertSame( $input, $result, 'Should not modify string input' );
	}

	public function testAddNotDoesNotProcessNullInput() {
		$state = [ 'not' => true ];

		$ta = new TexArray();
		$result = $ta->addNot( $state, null );

		$this->assertNull( $result, 'Should not modify null input' );
	}

	public function testUnshift() {
		$n = new TexArray( new TexNode( '' ) );
		$n->unshift( 'test1', 'test2' );
		$this->assertEquals( 3, $n->getLength(), 'Should unshift elements' );
	}

	public function testGenerator() {
		$ta = new TexArray( new Literal( 'a' ), new Literal( 'b' ) );
		foreach ( $ta as $item ) {
			$this->assertInstanceOf(
				'MediaWiki\Extension\Math\WikiTexVC\Nodes\Literal',
				$item,
				'Should iterate over the elements' );
		}
	}

	public function testOffsetExists() {
		$ta = new TexArray( new Literal( 'a' ), new Literal( 'b' ) );
		$this->assertTrue( isset( $ta[0] ) );
		$this->assertFalse( isset( $ta[2] ) );
	}

	public function testOffsetGet() {
		$ta = new TexArray( new Literal( 'a' ), new Literal( 'b' ) );
		$this->assertEquals( 'a', $ta[0]->render() );
		$this->assertNull( $ta[100] );
	}

	public function testOffsetUnset() {
		$ta = new TexArray( new Literal( 'a' ), new Literal( 'b' ) );
		unset( $ta[0] );
		$this->assertNull( $ta[0] );
	}

	public function testOffsetSet() {
		$ta = new TexArray();
		$ta[0] = new Literal( 'a' );
		$this->assertEquals( 'a', $ta[0]->render() );
	}

	public function testOffsetSetInvalid() {
		$this->expectException( InvalidArgumentException::class );
		$ta = new TexArray();
		$ta[0] = 'a';
	}

	public function testSquashLiterals() {
		$ta = new TexArray( new Literal( 'a' ), new Literal( 'b' ) );
		$state = [ 'squashLiterals' => true ];
		$res  = $ta->toMMLTree( [], $state );
		$this->assertEquals( '<mi>ab</mi>', $res );
	}

	public function testSquashLiteralsMacro() {
		$ta = new TexArray( new Literal( 'a' ), new Literal( '\gamma' ) );
		$state = [ 'squashLiterals' => true ];
		$res  = $ta->toMMLTree( [], $state );
		$this->assertEquals( '<mi>a</mi><mi>γ</mi>', $res );
	}

	public function testSumInLimits() {
		$ta = new TexArray();
		$sum = new Literal( '\sum' );
		$res  = $ta->checkForLimits( $sum, new DQ( new Literal( '\limits' ), new Literal( 'n' ) ) );
		$this->assertTrue( $res[1] );
		$this->assertEquals( $sum, $res[0] );
	}

	public function testCustomOpInLimits() {
		$ta = new TexArray();
		$custom = new Fun1nb( '\operatorname', new TexArray( new Literal( 'S' ) ) );
		$res  = $ta->checkForLimits( $custom, new DQ( new Literal( '\limits' ), new Literal( 'n' ) ) );
		$this->assertTrue( $res[1] );
		$this->assertEquals( $custom, $res[0] );
	}

	public function testRenderADeriv() {
		$n = new TexArray( new Literal( 'A' ) );
		$state = [ 'deriv' => 1 ];
		$mml = $n->toMMLTree( [], $state );
		$this->assertStringContainsString( '&#x2032;</mo>', $mml );
	}

	public function testRenderNullDeriv() {
		$n = new TexArray( new Literal( 'A' ) );
		$state = [ 'deriv' => 1 ];
		$mml = $n->addDerivativesContext( $state, null );
		$this->assertStringContainsString( '&#x2032;</mo>', $mml );
	}

	public function testRenderStringDeriv() {
		$n = new TexArray( new Literal( 'A' ) );
		$state = [ 'deriv' => 1 ];
		$mml = $n->addDerivativesContext( $state, "k" );
		$this->assertStringContainsString( '&#x2032;</mo>', $mml );
	}
}
