#include <bits/stdc++.h>
using namespace std;

using VS = vector<string>;    using LL = long long;
using VI = vector<int>;       using VVI = vector<VI>;
using PII = pair<int, int>;   using PLL = pair<LL, LL>;
using VL = vector<LL>;        using VVL = vector<VL>;

#define ALL(a)  begin((a)),end((a))
#define RALL(a) (a).rbegin(), (a).rend()
#define SZ(a) int((a).size())
#define SORT(c) sort(ALL((c)))
#define RSORT(c) sort(RALL((c)))
#define UNIQ(c) (c).erase(unique(ALL((c))), end((c)))
#define FOR(i, s, e) for (int(i) = (s); (i) < (e); (i)++)
#define FORR(i, s, e) for (int(i) = (s); (i) > (e); (i)--)
//#pragma GCC optimize ("-O3") 
#ifdef YANG33
#include "mydebug.hpp"
#else
#define DD(x) 
#endif
const int INF = 1e9;                          const LL LINF = 1e16;
const LL MOD = 1000000007;                    const double PI = acos(-1.0);
int DX[8] = { 0, 0, 1, -1, 1, 1, -1, -1 };    int DY[8] = { 1, -1, 0, 0, 1, -1, 1, -1 };

/* -----  2018/11/12  Problem: ARC 050 D / Link: http://arc050.contest.atcoder.jp/tasks/arc050_d  ----- */
/* ------問題------
-----問題ここまで----- */
/* -----解説等-----
文字列集合をつなげて辞書順最小をつくるときは、si<sjなら、si+sj<sj+siであるようにすればよい。
大小関係を求めたいが、連結すると当然TLEする。suffixを並べるので、LCPやらrankが使えそう。
i<jとすると、連結されたものについて、区間が3つに分かれる。
それぞれ場合分けすれば解ける。
1.先頭のa.bの共通部分がbの長さ未満の時：suffix[a],suffix[b]の大小関係を見ればよく、rank[a],rank[b]を見れば良い。
2.suffix[a]のaと、suffix[b]の続きのsuffix[a]の共通部分がその長さ未満の時：suffix[a]のprefixをlen(b)だけ削ったものはsuffix(c)と考えられる。
これについて、suffix[c],suffix[a]のrankを比べれば良い。
3.ソレ以降までlcpがあるとき：一致する(一致しないならば最初の比較に失敗しなければならないため)
結局、suffix集合であることによってO(1)の比較が可能。
----解説ここまで---- */

class SuffixArray
{
private:
	using byte = unsigned char;
	byte mask[8] = { 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 };
#define tget(i) !!(t[(i)>>3]&mask[(i)&7])
#define tset(i, b) t[(i)>>3]=(b) ? (mask[(i)&7]|t[(i)>>3]) : ((~mask[(i)&7])&t[(i)>>3])
#define chr(i) (cs==sizeof(int)?((int*)s)[i]:((byte *)s)[i])
#define isLMS(i) (i>0 && tget(i) && !tget(i-1))
	void getBuckets(byte *s, int *bkt, int n, int K, int cs, bool end = true) {
		fill(bkt, bkt + K + 1, 0);
		for (int i = 0; i < n; i++) {
			bkt[chr(i)]++;
		}
		for (int i = 0, tmp = 0; i < K + 1; i++) {
			tmp += bkt[i];
			bkt[i] = end ? tmp : tmp - bkt[i];
		}
	}
	void induceSAl(byte *t, byte *s, int *bkt, int n, int K, int cs) {
		getBuckets(s, bkt, n, K, cs, false);
		for (int i = 0; i < n; i++) {
			if (sa[i]>0 && !tget(sa[i] - 1)) {
				sa[bkt[chr(sa[i] - 1)]++] = sa[i] - 1;
			}
		}
	}
	void induceSAs(byte *t, byte *s, int *bkt, int n, int K, int cs) {
		getBuckets(s, bkt, n, K, cs, true);
		for (int i = n - 1; i >= 0; i--) {
			if (sa[i] > 0 && tget(sa[i] - 1)) {
				sa[--bkt[chr(sa[i] - 1)]] = sa[i] - 1;
			}
		}
	}
	void solve(byte *s, int n, int K = 128, int cs = 1) {
		byte t[(n >> 3) + 1];
		int bkt[K + 1], n1 = 0, name = 0;
		tset(n - 2, 0), tset(n - 1, 1);
		for (int i = n - 3; i >= 0; i--) {
			tset(i, (chr(i)<chr(i + 1) || (chr(i) == chr(i + 1) && tget(i + 1))));
		}
		getBuckets(s, bkt, n, K, cs);
		fill(sa, sa + n, -1);
		for (int i = 1; i < n; i++) {
			if (isLMS(i)) {
				sa[--bkt[chr(i)]] = i;
			}
		}
		induceSAl(t, s, bkt, n, K, cs);
		induceSAs(t, s, bkt, n, K, cs);
		for (int i = 0; i < n; i++) {
			if (isLMS(sa[i])) {
				sa[n1++] = sa[i];
			}
		}
		fill(sa + n1, sa + n, -1);
		for (int i = 0, tmp = -1; i < n1; i++) {
			int pos = sa[i], diff = false;
			for (int d = 0; d < n && !diff; d++) {
				diff = chr(pos + d) != chr(tmp + d) || tget(pos + d) != tget(tmp + d);
				if (!diff && d && (isLMS(pos + d) || isLMS(tmp + d))) break;
			}
			if (diff) {
				name++, tmp = pos;
			}
			sa[n1 + ((pos - (pos & 1)) >> 1)] = name - 1;
		}
		int* s1 = sa + n - n1;
		for (int i = n - 1, j = n - 1; i >= n1; i--) {
			if (sa[i] >= 0) {
				sa[j--] = sa[i];
			}
		}
		if (name < n1) {
			solve((byte*)s1, n1, name - 1, sizeof(int));
		}
		else {
			for (int i = 0; i < n1; i++) {
				sa[s1[i]] = i;
			}
		}
		getBuckets(s, bkt, n, K, cs);
		for (int i = 1, j = 0; i < n; i++) {
			if (isLMS(i)) {
				s1[j++] = i;
			}
		}
		for (int i = 0; i < n1; i++) {
			sa[i] = s1[sa[i]];
		}
		fill(sa + n1, sa + n, -1);
		for (int i = n1 - 1; i >= 0; i--) {
			int tmp = sa[i];
			sa[i] = -1, sa[--bkt[chr(tmp)]] = tmp;
		}
		induceSAl(t, s, bkt, n, K, cs);
		induceSAs(t, s, bkt, n, K, cs);
	}
public:
	bool contain(const string& T) {
		int a = 0, b = sz;
		while (b - a > 1) {
			int c = (a + b) / 2;
			if (CS.compare(sa[c], T.length(), T) < 0) {
				a = c;
			}
			else {
				b = c;
			}
		}
		return CS.compare(sa[b], T.length(), T) == 0;
	}
	string CS;
	byte* S;
	int sz;
	int* sa;
	SuffixArray(string arg) {
		CS = arg;
		sz = (int)arg.size();
		sa = new int[sz + 1];
		S = (byte*)arg.c_str();
		solve(S, sz + 1);
	}
};

template< typename MeetSemilattice>
struct SparseTable {
	using T = typename MeetSemilattice::t;
	vector<vector<T>> st;

	SparseTable() {}

	SparseTable(const vector<T> &v) {
		int b = 0;
		while ((1 << b) <= v.size()) ++b;
		st.assign(b, vector<T>(1 << b));
		for (int i = 0; i < v.size(); i++) {
			st[0][i] = v[i];
		}
		for (int i = 1; i < b; i++) {
			for (int j = 0; j + (1 << i) <= (1 << b); j++) {
				st[i][j] = MeetSemilattice::op(st[i - 1][j], st[i - 1][j + (1 << (i - 1))]);
			}
		}
	}
	inline T query(int l, int r) {
		if (l > r)swap(l, r);
		int b = 32 - __builtin_clz(r - l) - 1;
		return (MeetSemilattice::op(st[b][l], st[b][r - (1 << b)]));
	}
};

VI make_lcp(const string s, const VI &sa, const VI&rank) {
	VI LCP;
	LCP.resize(s.size());
	LCP[0] = 0;
	for (int i = 0, h = 0; i < s.size(); i++) {
		if (rank[i] + 1 < s.size()) {
			for (int j = sa[rank[i] + 1]; max(i, j) + h < s.length() && s[i + h] == s[j + h]; ++h);
			LCP[rank[i] + 1] = h;
			if (h > 0) --h;
		}
	}
	return std::move(LCP);
}

struct LCPs {
	struct M {
		using t = int;
		static t op(const t&l, const t&r) { return min(l, r); }
	};
	SparseTable<M> seg;
	vector<int> rank;
	LCPs(const string &s) {
		vector<int>sa; {
			SuffixArray suffixarray(s);
			sa = vector<int>((int)s.size());
			for (int i = 0; i < (int)s.size(); ++i) {
				sa[i] = (suffixarray.sa[i+1]);
			}
		}
		rank = vector<int>((int)s.size());
		for (int i = 0; i < (int)s.size(); ++i) {
			rank[sa[i]] = i;
		}
		vector<int>LCP = make_lcp(s, sa, rank);
		seg = SparseTable<M>(LCP);

	}
	int lcp(int i, int j) { //s[i..] s[j..]
		if (rank[i] > rank[j]) {
			return seg.query(rank[j] + 1, rank[i] + 1);
		}
		else {
			return seg.query(rank[i] + 1, rank[j] + 1);
		}
	}
};




int main() {
	cin.tie(0);
	ios_base::sync_with_stdio(false);

	string s; cin >> s;
	// 全部一緒(N,1)
	{
		int same = 1;
		FOR(i, 0, SZ(s)) {
			same &= s.front() == s[i];
		}
		if (same) {
			cout << SZ(s) << endl;
			cout << 1 << endl;
			return 0;
		}
	}
	// 周期判定(1,1)
	{
		LCPs lcps(s);
		int periodOne = 1;
		// どのような分割も周期をもたない
		FOR(i, 0, SZ(s) - 1) {
			int prefixsz = i + 1;
			if (SZ(s) % prefixsz == 0) {

				int periodLCP = lcps.lcp(0, i + 1);
				//				int periodLCP = rh.LCP(rh, 0, SZ(s), i + 1, SZ(s));
				periodOne &= (periodLCP < SZ(s) - prefixsz);
			}
		}
		if (periodOne) {
			cout << 1 << endl;
			cout << 1 << endl;
			return 0;
		}
	}
	// その他 (2,N-1-分割しちゃダメ)
	{
		// WA
		// 最後のケースはnodiv[k]=1が存在しない
		auto f = [](const string& s) {
			LCPs lcps(s);
			VI noDivide(SZ(s), 0);
			FOR(prefixsz, 1, SZ(s)) {
				for (int k = 2 * prefixsz; k < SZ(s); k += prefixsz) {
					if (lcps.lcp(0, prefixsz) >= k - prefixsz) {
						noDivide[k - 1] = 1;
					}
					else break;
				}
			}
			return noDivide;
		};
		// 前から
		auto prefixdiv = f(s);
		// 後ろから
		string t = s;
		reverse(ALL(t));
		auto suffixdiv = f(t);
		reverse(ALL(suffixdiv));


		int ans = SZ(s) - 1;
		FOR(i, 0, SZ(s) - 1) {
			ans -= (prefixdiv[i] || suffixdiv[i + 1]);
		}
		cout << 2 << endl;
		cout << ans << endl;
	}




	return 0;
}


Measured memory_bytes: 56752