#!/usr/bin/env python3
import random
import fractions


class System():

    def __str__(self):
        return str('\n'.join([', '.join(list(map(str, i))) for i in self.coeffs]))

    def __init__(self, coeffs, quiet=True, text=None, parent=None):
        if text:
            pass
        else:
            self.coeffs = [[fractions.Fraction(j) for j in i] for i in coeffs]
        self.quiet = quiet
        self.parent = parent
        self.proper_offset = 0

    def crop(self):
        self.proper_offset += 1
        self.coeffs = [[j for index,j in enumerate(i) if index > 0] for i in self.coeffs]
        return self

    def triangularise(self):
        # Find a good pivot
        if len(self.coeffs) == 1:
            return self
        pivot = 0
        for i, line in enumerate(self.coeffs):
            if not self.coeffs[pivot][0] or line[0] == 0:
                pivot = i
                continue
            if line[0] == 1:
                pivot = i
                break
            if line[0].denominator == 1:
                if line[0] < self.coeffs[pivot][0]:
                    pivot = i
                    continue
            else:
                if line[0].denominator < self.coeffs[pivot][0].denominator:
                    pivot = i
                    continue
        self.swap(0, pivot)
        # If we couldn't find a pivot, the first coeff of each line is 0.
        # That means the system is not of the defined dimension.
        # Let's fix it.
        if self.coeffs[0][0] == 0:
            self.crop()
            return self.triangularise()

        # Cancel the first coefficient of each line
        for i in range(1, len(self.coeffs)):
            factor = self.coeffs[i][0] / self.coeffs[0][0]
            #print()
            #print(self)
            self.add(i, 0, factor)
            #print(self)
            #print()
        # Triangularise the subsystem
        sub = self.subsystem().triangularise()
        for i in range(len(sub.coeffs)):
            for j in range(len(sub.coeffs[0])):
                self.coeffs[i + sub.lineoffset() - self.lineoffset()][j + sub.offset() - self.offset()] = sub.coeffs[i][j]
        return self

    def add(self, l1, l2, factor):
        sl1,sl2 = [self.lineoffset() + i + 1 for i in [l1,l2]]
        print("L%s <- L%s + (%s)*L%s"%(str(sl1), str(sl1), str(factor), str(sl2)))
        term = [i*factor for i in self.coeffs[l2]]
        res = [self.coeffs[l1][i] - term[i] for i in range(len(self.coeffs[0]))]
        self.coeffs[l1] = res
        return self

    def swap(self, l1, l2):
        self.coeffs[l1], self.coeffs[l2] = self.coeffs[l2], self.coeffs[1]
        return self

    def offset(self):
        if self.parent:
            return self.parent.offset() + self.proper_offset + 1
        else:
            return self.proper_offset

    def lineoffset(self):
        if self.parent:
            return self.parent.lineoffset() + 1
        else:
            return 0

    def subsystem(self):
        print(self)
        subcoefs = [[j for j in i[1:]] for i in self.coeffs[1:]]
        return self.__class__(subcoefs, parent=self)

k=3
test = System([[fractions.Fraction(str(random.randint(1,50))+'/'+str(1+0*random.randint(1,10))) for j in range(k+1)] for i in range(k)], quiet=False)
print(test.triangularise())
"""class Systeme():
    def __str__(self):
        return '\n'.join([''.join([((str(self.coeffs[i][j])) + ["x" + str(j+self.offset),"tbcdefghijklmnopqrstuvwxyz"[(j+23+self.offset)%26]][True] + (' + ' if self.coeffs[i][j+1:-1].count(0) == 0 else ' ') if self.coeffs[i][j] else '' ) if j != len(self.coeffs[0]) - 1 else "= " + str(self.coeffs[i][j]) for j in range(len(self.coeffs[0]))]) for i in range(len(self.coeffs))])

    def __init__(self, coeffs, quiet=True, text=None, parent=None, offset=0):
        if text:
            self.coeffs=[list(map(fractions.Fraction, map(int, i.split(',')))) for i in text.split(';')]
        else:
            self.coeffs = [[fractions.Fraction(j) for j in i] for i in coeffs]
        self.quiet=quiet
        self.parent=parent
        self.offset=offset
    def pivot(self, new_pivot):
        self.quiet or print("L%s <-> L"%(str(self.offset + 1))+ str(new_pivot+1+self.offset))
        self.coeffs[0], self.coeffs[new_pivot] = self.coeffs[new_pivot], self.coeffs[0]
        return self

    def cancel(self, line):
        if line == 0:
            return self
        if self.coeffs[0][0] == 0:
            return self
        line_coeff = self.coeffs[line][0] / self.coeffs[0][0]
        self.quiet or print("L"+str(line+1+self.offset) + " <- L" + str(line+1+self.offset) + " - ("+str(line_coeff)+")*L"+str(self.offset+1))
        self.coeffs[line] = [self.coeffs[line][i] - line_coeff * self.coeffs[0][i] for i in range(len(self.coeffs[line]))]
        return self

    def solve(self):
        self.quiet or print("\nSolving system of dimension : ", len(self.coeffs))
        self.quiet or print(self)
        
        if len(self.coeffs) == 1:
            return self
        
#Selecting pivot
        pivot = 0
        for i in range(len(self.coeffs)):
            if self.coeffs[i][0] != 0 and (self.coeffs[i][0] < self.coeffs[pivot][0] or self.coeffs[pivot][0]==0) :
                pivot = i
        self.pivot(pivot)
        
#Canceling the first coeffs of each equation (except the first one)
        self.quiet or print("\n<=>\n")
        for i in range(1, len(self.coeffs)):
            self.cancel(i)
        self.quiet or print(self)
        
#Solving the subsystem
        for i, line in enumerate(Systeme([[self.coeffs[i][j] for j in range(1, len(self.coeffs[i]))]for i in range(1, len(self.coeffs))], quiet=self.quiet, parent=self, offset=(self.offset+1)).solve().coeffs):
            for j in range(len(line)):
                self.coeffs[i+1][j+1] = line[j]
        self.quiet or print()
        return self

    def find(self, this):
        if self.coeffs[this][:-1].count(0) < len(self.coeffs[this][:-1]) - 1:
            self.cleanline(this)
        if self.coeffs[this][:-1].count(0) == len(self.coeffs[this][:-1]) - 1:
            if self.coeffs[this][this]==1:
                return self.coeffs[this][-1]
            else:
                self.coeffs[this][-1], self.coeffs[this][this]  = self.coeffs[this][-1] / self.coeffs[this][this], 1
                return self.coeffs[this][-1]
        raise Exception("Unicity error")
                    
    def cleanline(self, line):
        for i in range(len(self.coeffs[line][:-1:])-1, 0-1, -1):
            if len(self.coeffs[line][:-1]) - self.coeffs[line][:-1].count(0) <= 1:
                return self
            if self.coeffs[line][i] !=0:
                self.coeffs[line][-1], self.coeffs[line][i]=self.coeffs[line][-1] - self.coeffs[line][i]*self.find(i), 0
        return self
    def clean(self):
        self.find(0)
        return self
"""
"""
print(test)
print()
print(test.solve())
print()
print(test.clean())"""
