#include <iostream>
#include <iomanip>
#include <stdio.h>
#include <set>
#include <vector>
#include <map>
#include <cmath>
#include <algorithm>
#include <memory.h>
#include <string>
#include <sstream>
#include <cstdlib>
#include <ctime>
#include <cassert>

using namespace std;

typedef long long LL;
typedef pair<int,int> PII;

#define MP make_pair
#define PB push_back
#define FF first
#define SS second

#define FORN(i, n) for (int i = 0; i <  (int)(n); i++)
#define FOR1(i, n) for (int i = 1; i <= (int)(n); i++)
#define FORD(i, n) for (int i = (int)(n) - 1; i >= 0; i--)

#define DEBUG(X) { cout << #X << " = " << (X) << endl; }
#define PR0(A,n) { cout << #A << " = "; FORN(_,n) cout << A[_] << ' '; cout << endl; }

#define MOD 1000000007
#define INF 2000000000

int GLL(LL& x) {
    return scanf("%lld", &x);
}

int GI(int& x) {
    return scanf("%d", &x);
}

const LL LLINF = 1LL * INF * INF;

LL choose[410][5], fac[5], ex[5][5];

int n;
LL c1[4], c2[4], c3[4], c4[4];

vector<LL> tiles;
map<LL,LL> tileCnt;

void rot4(LL arr[4]) {
    swap(arr[0], arr[1]);
    swap(arr[1], arr[2]);
    swap(arr[2], arr[3]);
}

void reflect(LL arr[4]) {
    swap(arr[0], arr[3]);
    swap(arr[1], arr[2]);
}

void load(LL arr[4], LL t) {
    FORD(i, 4) {
        arr[i] = t % 1000;
        t /= 1000;
    }
}

LL rep(LL arr[4]) {
    LL base = 1; LL res = 0;

    FORD(i, 4) {
        res += arr[i] * base;
        base *= 1000;
    }

    return res;
}

LL minRep(LL arr[4]) {
    LL best = LLINF;

    FORN(i, 4) {
        best = min(best, rep(arr));
        rot4(arr);
    }
    
    return best;
}

int main() {
    choose[1][0] = choose[1][1] = 1;
    for (int i = 2; i < 410; i++) {
        choose[i][0] = 1;
        for (int j = 1; j < 5; j++) {
            choose[i][j] = choose[i-1][j-1] + choose[i-1][j];
        }
    }

    fac[0] = 1; fac[1] = 1; fac[2] = 2; fac[3] = 6; fac[4] = 24;

    FORN(i, 5) {
        ex[i][0] = 1;
        FOR1(j, 4) ex[i][j] = ex[i][j-1] * i;
    }

    GI(n);

    FORN(i, n) {
        FORN(j, 4) GLL(c1[j]);

        LL mr = minRep(c1);
        tiles.PB(mr); tileCnt[mr]++;
    }

//    PR0(tiles, n);
    
    LL res = 0;
    
    FORN(i, tiles.size()) { // top tile
        tileCnt[tiles[i]]--; load(c1, tiles[i]);

        for (int j = i + 1; j < tiles.size(); j++) { // bottom tile
            tileCnt[tiles[j]]--; load(c2, tiles[j]); reflect(c2);

            FORN(k, 4) { // for each rotation of the bottom tile
                map<LL,LL> need; LL curr = 1;

                FORN(l, 4) { // populate the tiles we need
                    c3[0] = c1[l];
                    c3[1] = c1[(l+3)%4];
                    c3[2] = c2[(l+3)%4];
                    c3[3] = c2[l];
                    need[minRep(c3)]++;
                }

                for (auto& kv : need) {
                    LL needTile = kv.FF; LL needCnt = kv.SS;
                    
                    if (tileCnt[needTile] < needCnt) {
                        curr *= 0;
                    }
                    else {
                        // count rotational symmetries of needTile
                        load(c3, needTile); LL rotSym = 0;

                        FORN(m, 4) {
                            rotSym += (rep(c3) == needTile);
                            rot4(c3);
                        }

                        // choices * tile ordering * rotations
                        curr *= choose[tileCnt[needTile]][needCnt] * fac[needCnt] * ex[rotSym][needCnt];
                    }
                }

                res += curr;
                rot4(c2);
            }
            tileCnt[tiles[j]]++;
        }
        tileCnt[tiles[i]]++;
    }

    cout << res / 3 << "\n";

    return 0;
}

Measured memory_bytes: 80128