#define _CRT_SECURE_NO_WARNINGS
#define _USE_MATH_DEFINES
#include <iostream>
#include <iomanip>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <cstdlib>
#include <algorithm>
#include <string>
#include <vector>
#include <stack>
#include <queue>
#include <set>
#include <map>
#include <functional>
#include <utility>
#include <tuple>
#include <cctype>
#include <bitset>
#define int long long
using namespace std;
#define INF 0x3f3f3f3f
#define INFLL 0x3f3f3f3f3f3f3f3fLL
#define MOD 1000000007
#define mp make_pair
#define mt make_tuple
#define pb push_back
typedef long long ll;
typedef long double ld;
typedef pair<int, int> pint;
typedef pair<ll,ll> pll;
typedef tuple<int,int,int> tint;
typedef vector<int> vint;
typedef vector<ll> vll;
int dx[8]={0,0,-1,1,1,1,-1,-1};
int dy[8]={-1,1,0,0,1,-1,1,-1};
const int SIZE=100050;
//ここまでテンプレ
#define rep(i,a,n) for(int i=a; i<n; i++)
#define repq(i,a,n) for(int i=a; i<=n; i++)
ll fact_mod(ll n, ll mod) {
    ll f = 1; repq(i,2,n) f = f * (i % MOD) % MOD;
    return f;
}
// 繰り返し二乗法 (再帰バージョン)
ll mod_pow(ll x, ll n, ll mod) {
    if(n == 0) return 1;
    ll res = mod_pow((x * x) % mod, n / 2 , mod);
    if(n & 1) res = (res * x) % mod;
    return res;
}
// 組み合わせ nCr を求める (modあり)
ll combination_mod(ll n, ll r, ll mod) {
    if(r > n-r) r = n-r;
    if(r == 0) return 1;
    ll a = 1;
    rep(i, 0, r) a = a * ((n-i) % mod) % mod;
    ll b = mod_pow(fact_mod(r, mod), mod-2, mod);
    return (a % mod) * (b % mod) % mod;
}
//4つの色を辞書順最小の並びで返す
vint COLOR(int a,int b,int c,int d){
	vint A={a,b,c,d},B={b,c,d,a},C={c,d,a,b},D={d,a,b,c};
	vector<vint> vec={A,B,C,D};
	sort(vec.begin(),vec.end());
	return vec[0];
}
signed main(){
	int N;
	cin>>N;
	map<vint,int> pam;
	vector<vint> panel;
	//それぞれのパネルに含まれる色を、昇順にソートして入れていく
	for(int i=0;i<N;i++){
		int a,b,c,d;
		cin>>a>>b>>c>>d;
		//4つの角の順番を変えないように、辞書順最小の並びに揃える
		vint v=COLOR(a,b,c,d);
		pam[v]++;
		panel.pb(v);
	}
	ll ans=0;
	//立方体の底に使うパネルiと天井に使うパネルjを全探索する
	for(int i=0;i<N;i++){
		for(int j=0;j<i;j++){
			//天井のパネルの向きを全探索する
			for(int k=0;k<4;k++){
				vint cei,flo;
				for(int l=0;l<4;l++){
					flo.pb(panel[i][l]);
					cei.pb(panel[j][(k+l)%4]);
				}
				//側面の4つのパネルになりうるパネルがあるか？
				vint A=COLOR(cei[0],flo[0],flo[3],cei[1]);
				vint B=COLOR(cei[3],flo[1],flo[0],cei[0]);
				vint C=COLOR(cei[2],flo[2],flo[1],cei[3]);
				vint D=COLOR(cei[1],flo[3],flo[2],cei[2]);
				//pamから底と天井に使ったパネルを引く
				pam[panel[i]]--;
				pam[panel[j]]--;
				//立方体が何通りできるか？
				//側面に使うパネル
				map<vint,int> use;
				use[A]++,use[B]++,use[C]++,use[D]++;
				if(pam.find(A)==pam.end() || pam.find(B)==pam.end() || pam.find(C)==pam.end() || pam.find(D)==pam.end());
				else if(pam[A]<use[A] || pam[B]<use[B] || pam[C]<use[C] || pam[D]<use[D]);
				else{
					ll temp=1;
					for(pair<vint,int> P:use){
						temp*=combination_mod(pam[P.first],P.second,(1<<31)-1);
						if(P.first[1]==P.first[0] && P.first[2]==P.first[0] && P.first[3]==P.first[0])
							for(int l=0;l<P.second;l++)
								temp*=4;
						else if(P.first[0]==P.first[2] && P.first[1]==P.first[3])
							for(int l=0;l<P.second;l++)
								temp*=2;
						for(int l=1;l<=P.second;l++)
							temp*=l;
					}
					ans+=temp;
				}
				//pamに底と天井に使ったパネルを足す
				pam[panel[i]]++;
				pam[panel[j]]++;
			}
		}
	}
	cout<<ans/3<<endl;
	return 0;
}

Measured memory_bytes: 384