/+ dub.sdl:
    name "E"
    dependency "dcomp" version=">=0.1.2"
+/

import std.stdio, std.datetime, std.range, std.algorithm, std.conv;

// import dcomp.scanner, dcomp.matrix, dcomp.numeric;

alias Mint = ModInt!(10^^9 + 7);

int[4] normalize(int[4] x) {
    auto mi = x;
    foreach (i; 0..4) {
        mi = min(mi, x);
        bringToFront(x[0..1], x[1..4]);
    }
    return mi;
}

//duplicate number
int num(int[4] x) {
    auto y = x;
    int co = 0;
    foreach (i; 0..4) {
        if (x == y) co++;
        bringToFront(y[0..1], y[1..$]);
    }
    return co;
}

int[4][] col;

int[int[4]] colMp;

long F(int n, int k) {
    if (n < k || n < 0) return 0;
    long ans = 1;
    foreach (i; 0..k) {
        ans *= n-i;
    }
    return ans;
}
long cnt(int a, int b, int[4][4] x) {
    //[0,,a)
    colMp[col[b]]--;
    int[int[4]] mp;
    x.each!(a => mp[a]++);
    long ans = 1;
    foreach (v, k; mp) {
        if (!(v in colMp)) {
            ans = 0;
            continue;
        }
        int n = colMp[v];
        int m = num(v);
        //nからk個選ぶ,それぞれ重複度m
        ans *= pow(m, k);
        ans *= F(n, k);
    }
    
    colMp[col[b]]++;
    return ans;
}
int main(string[] argv) {
    Scanner sc = new Scanner();
    int n;
    sc.read(n);
    col = new int[4][](n);
    foreach (ref v; col) {
        sc.read(v);
        v = v.normalize;
    }

    long ans = 0;
    foreach (i; 0..n) {
        foreach (j; 0..i) {
            int[4] a = col[i], b = col[j];
            foreach (d; 0..4) {
                int[4][4] x;
                x[0] = [a[1], a[0], b[1], b[0]];
                x[1] = [a[2], a[1], b[0], b[3]];
                x[2] = [a[3], a[2], b[3], b[2]];
                x[3] = [a[0], a[3], b[2], b[1]];
                x[].each!((ref a) => a = a.normalize);
                ans += cnt(i, j, x);
                bringToFront(b[0..1], b[1..4]);
            }
        }
        colMp[col[i]]++;
    }
    writeln(ans);
    return 0;
}
/* IMPORT /Users/yosupo/Program/dcomp/source/dcomp/matrix.d */
// module dcomp.matrix;

struct SMatrix(T, size_t H, size_t W) {
    T[W][H] data;
    this(Args...)(Args args) {
        static assert(args.length == H*W);
        foreach (i, v; args) {
            data[i/W][i%W] = v;
        }
    }

    @property static size_t height() {return H;}
    @property static size_t width() {return W;}

    auto ref opIndex(size_t i1, size_t i2) {
        return data[i1][i2];
    }
    auto opBinary(string op:"+", R)(R r)
    if(height == R.height && width == R.width) {
        auto res = this;
        foreach (y; 0..height) foreach (x; 0..W) res[y, x] += r[y, x];
        return res;
    }
    auto opBinary(string op:"*", R)(R r)
    if(width == R.height) {
        auto res = SMatrix!(T, height, R.width)();
        foreach (y; 0..height) {
            foreach (x; 0..R.width) {
                foreach (k; 0..width) {
                    res[y, x] += this[y, k]*r[k, x];
                }
            }
        }
        return res;
    }
    auto opOpAssign(string op, T)(T r) {return mixin ("this=this"~op~"r");}    
}

auto matrix(size_t H, size_t W, alias pred)() {
    import std.traits : ReturnType;
    SMatrix!(typeof(pred(0, 0)), H, W) res;
    foreach (y; 0..H) {
        foreach (x; 0..W) {
            res[y, x] = pred(y, x);
        }
    }
    return res;
}
/* IMPORT /Users/yosupo/Program/dcomp/source/dcomp/scanner.d */
// module dcomp.scanner;

class Scanner {
    import std.stdio : File, stdin;
    import std.conv : to;
    import std.range : front, popFront, array, ElementType;
    import std.array : split;
    import std.traits : isSomeChar, isStaticArray, isArray; 
    import std.algorithm : map;
    File f;
    this(File f = stdin) {
        this.f = f;
    }
    string[] buf;
    private bool succ() {
        while (!buf.length) {
            if (f.eof) return false;
            buf = f.readln.split;
        }
        return true;
    }
    private bool readSingle(T)(ref T x) {
        if (!succ()) return false;
        static if (isArray!T) {
            alias E = ElementType!T;
            static if (isSomeChar!E) {
                //string or char[10] etc
                x = buf.front;
                buf.popFront;
            } else {
                static if (isStaticArray!T) {
                    //static
                    assert(buf.length == T.length);
                }
                x = buf.map!(to!E).array;
                buf.length = 0;                
            }
        } else {
            x = buf.front.to!T;
            buf.popFront;            
        }
        return true;
    }
    int read(T, Args...)(ref T x, auto ref Args args) {
        if (!readSingle(x)) return 0;
        static if (args.length == 0) {
            return 1;
        } else {
            return 1 + read(args);
        }
    }
}

unittest {
    import std.path : buildPath;
    import std.file : tempDir;
    import std.algorithm : equal;
    import std.stdio : File;
    string fileName = buildPath(tempDir, "kyuridenanmaida.txt");
    auto fout = File(fileName, "w");
    fout.writeln("1 2 3");
    fout.writeln("ab cde");
    fout.writeln("1.0 1.0 2.0");
    fout.close;
    Scanner sc = new Scanner(File(fileName, "r"));
    int a;
    int[2] b;
    char[2] c;
    string d;
    double e;
    double[] f;
    sc.read(a, b, c, d, e, f);
    assert(a == 1);
    assert(equal(b[], [2, 3]));
    assert(equal(c[], "ab"));
    assert(equal(d, "cde"));
    assert(e == 1.0);
    assert(equal(f, [1.0, 2.0]));
}
/* IMPORT /Users/yosupo/Program/dcomp/source/dcomp/numeric.d */
// module dcomp.numeric;


T pow(T, U)(T x, U n, T e) {
    while (n) {
        if (n & 1) e *= x;
        x *= x;
        n /= 2;
    }
    return e;
}
T pow(T, U)(T x, U n) {
    return pow(x, n, T(1));
}

T lcm(T)(in T a, in T b) {
    import std.numeric : gcd, abs;
    return a / gcd(a,b) * b;
}

//a*T[0]+b*T[1]=T[2], T[2]=gcd
//todo: to binary extgcd
T[3] extGcd(T)(T a, T b) {
    if (b==0) {
        return [1, 0, a];
    } else {
        auto e = extGcd(b, a%b);
        return [e[1], e[0]-a/b*e[1], e[2]];
    }
}

struct ModInt(uint MD) {
    import std.conv : to;
    uint v;
    this(int v) {this.v = (v%MD+MD)%MD;}
    this(long v) {this.v = (v%MD+MD)%MD;}
    auto normS(uint x) {return (x<MD)?x:x-MD;}
    auto make(uint x) {ModInt m; m.v = x; return m;}
    auto opBinary(string op:"+")(ModInt r) {return make(normS(v+r.v));}
    auto opBinary(string op:"-")(ModInt r) {return make(normS(v+MD-r.v));}
    auto opBinary(string op:"*")(ModInt r) {return make((v.to!long*r.v%MD).to!uint);}
    auto opBinary(string op:"/")(ModInt r) {return this*inv(r);}
    auto opOpAssign(string op)(ModInt r) {return mixin ("this=this"~op~"r");}
    static ModInt inv(ModInt x) {return ModInt(extGcd(x.v, MD)[0]);}
    string toString() {return v.to!string;}
}

// f([10]) = [1*] 
// &演算での畳み込みを内積に変換する
T[] zeta(T)(T[] v, bool rev) {
    import core.bitop : bsr;
    int n = bsr(v.length);
    assert(1<<n == v.length);
    foreach (fe; 0..n) {
        foreach (i, _; v) {
            if (i & (1<<fe)) continue;
            if (!rev) {
                v[i] += v[i|(1<<fe)];
            } else {
                v[i] -= v[i|(1<<fe)];
            }
        }
    }
    return v;
}

// xor演算での畳み込みを内積に変換する、サイズ2のFFT
T[] hadamard(T)(T[] v, bool rev) {
    import core.bitop : bsr;
    int n = bsr(v.length);
    assert(1<<n == v.length);
    foreach (fe; 0..n) {
        foreach (i, _; v) {
            if (i & (1<<fe)) continue;
            auto l = v[i], r = v[i|(1<<fe)];
            if (!rev) {
                v[i] = l+r;
                v[i|(1<<fe)] = l-r;
            } else {
                v[i] = (l+r)/2;
                v[i|(1<<fe)] = (l-r)/2;
            }
        }
    }
    return v;
}


Measured memory_bytes: 1276