unittest
{
	assert( [ "3 3", "...", "...", "..." ].parse.expand.solve == 4 );
	assert( [ "3 5", "...#.", ".#.#.", ".#..." ].parse.expand.solve == 10 );
	
	assert( [ "20 20" ].chain( "....................".repeat( 20 ) ).parse.expand.solve == 38 );		// 追加ケース
}



import std.algorithm;
import std.conv;
import std.range;
import std.stdio;
import std.typecons;

void main()
{
	stdin.byLineCopy.parse.expand.solve.writeln;
}

auto parse( Range )( Range input )
if( isInputRange!Range && is( ElementType!Range == string ) )
{
	auto hw = input.front.split.to!( long[] );
	auto sss = new dchar[][]( hw[ 0 ], hw[ 1 ] );
	foreach( ref ss; sss )
	{
		input.popFront;
		ss = input.front.to!( dchar[] );
	}
	return tuple( sss );
}

auto solve( dchar[][] sss )
{
	auto ans = 0L;
	foreach( sy; 0 .. sss.length )
	{
		foreach( sx; 0 .. sss[ sy ].length )
		{
			if( sss[ sy ][ sx ] == '.' ) ans = max( ans, bfsGridEx( sss, sy, sx ) );
		}
	}
	return ans;
}

// グリッドマップの始点指定最短経路探索（幅優先探索）
auto bfsGridEx( T )( T[][] css, long sy, long sx, T w = '#' )
{
	import std.container.dlist;
	
	auto m = 0L;
	auto dss = new long[][]( css.length, css[ 0 ].length );
	foreach( ref ds; dss ) ds[] = long.max;
	dss[ sy ][ sx ] = 0;
	
	auto queue = make!DList( tuple( sy, sx ) );
	while( !queue.empty )
	{
		auto fy = queue.front[ 0 ];
		auto fx = queue.front[ 1 ];
		queue.removeFront();
		
		foreach( p; [ [ -1, 0 ], [ 1, 0 ], [ 0, -1 ], [ 0, 1 ] ] )
		{
			auto ty = fy + p[ 0 ];
			auto tx = fx + p[ 1 ];
			
			if( ty < 0 || css.length <= ty || tx < 0 || css[ ty ].length <= tx ) continue;		// マップ外
			if( dss[ ty ][ tx ] < long.max ) continue;											// 探索済
			if( css[ ty ][ tx ] == w ) continue;												// 壁（侵入不可）
			
			// 道（1ターン使用）
			dss[ ty ][ tx ] = dss[ fy ][ fx ] + 1;
			queue.insertBack( tuple( ty, tx ) );
			
			// 一番長い道を更新
			m = max( m, dss[ ty ][ tx ] );
		}
	}
	return m;
}
