class FixedComponent(db.Model):
    __tablename__ = 'components_fixed'

    fixedcomponent_id = db.Column(db.Integer, primary_key=True)
    level_id = db.Column(db.Integer, db.ForeignKey('levels.level_id'))
    type = db.Column(db.String(255))
    x = db.Column(db.Integer)
    y = db.Column(db.Integer)

    level = db.relationship('Level',
                            backref=db.backref(
                                'fixedcomponents',
                                order_by=(x, y),
                                cascade='save-update, merge, delete'))
class Pipe(db.Model):
    __tablename__ = 'pipes'

    pipe_id = db.Column(db.Integer, primary_key=True)
    component_id = db.Column(db.Integer,
                             db.ForeignKey('components.component_id'))
    output_id = db.Column(db.Integer)
    x = db.Column(db.Integer)
    y = db.Column(db.Integer)

    component = db.relationship('Component',
                                backref=db.backref(
                                    'pipes',
                                    cascade='save-update, merge, delete'))

    def __init__(self, component_id, output_id, x, y):
        self.component_id = component_id
        self.output_id = output_id
        self.x = x
        self.y = y
class Component(db.Model):
    __tablename__ = 'components'

    component_id = db.Column(db.Integer, primary_key=True)
    solution_id = db.Column(db.Integer, db.ForeignKey('solutions.solution_id'))
    type = db.Column(db.String(255))
    x = db.Column(db.Integer)
    y = db.Column(db.Integer)

    solution = db.relationship('Solution',
                               backref=db.backref(
                                   'components',
                                   order_by=(x, y),
                                   cascade='save-update, merge, delete'))

    def __init__(self, solution_id, type, x, y):
        self.solution_id = solution_id
        self.type = type
        self.x = x
        self.y = y
class Member(db.Model):
    __tablename__ = 'members'

    member_id = db.Column(db.Integer, primary_key=True)
    component_id = db.Column(db.Integer,
                             db.ForeignKey('components.component_id'))
    type = db.Column(db.String(255))
    arrow_dir = db.Column(db.Integer)
    choice = db.Column(db.Integer)
    layer = db.Column(db.Integer)
    x = db.Column(db.Integer)
    y = db.Column(db.Integer)
    element_type = db.Column(db.Integer)
    element = db.Column(db.Integer)

    component = db.relationship('Component',
                                backref=db.backref(
                                    'members',
                                    cascade='save-update, merge, delete'))

    @property
    def color(self):
        if self.layer in (16, 32):
            return "blue"
        elif self.layer in (64, 128):
            return "red"
        else:
            return "feature"

    ARROW_DIRS = {180: "l", -90: "u", 0: "r", 90: "d"}
    ELEMENTS = str.split(
        'XX H He Li Be B C N O F Ne Na Mg Al Si P S Cl Ar K '
        'Ca Sc Ti V Cr Mn Fe Co Ni Cu Zn Ga Ge As Se Br Kr '
        'Rb Sr Y Zr Nb Mo Tc Ru Rh Pd Ag Cd In Sn Sb Te I Xe '
        'Cs Ba La Ce Pr Nd Pm Sm Eu Gd Tb Dy Ho Er Tm Yb Lu '
        'Hf Ta W Re Os Ir Pt Au Hg Tl Pb Bi Po At Rn Fr Ra '
        'Ac Th Pa U Np Pu Am Cm Bk Cf Es Fm Md No Lr Rf Db Sg '
        'Bh Hs Mt')

    def __init__(self, component_id, type, arrow_dir, choice, layer, x, y,
                 element_type, element):
        self.component_id = component_id
        self.type = type
        self.arrow_dir = arrow_dir
        self.choice = choice
        self.layer = layer
        self.x = x
        self.y = y
        self.element_type = element_type
        self.element = element

    @property
    def image_name(self):
        variant = ""

        if self.type == "feature-bonder":
            return "feature-bonder.png"
        elif self.type == "feature-bonder-minus":
            return "feature-bonder_minus.png"
        elif self.type == "feature-bonder-plus":
            return "feature-bonder_plus.png"
        elif self.type == "feature-fuser":
            return "feature-fuser.png"
        elif self.type == "feature-sensor":
            return "feature-sensor.png"
        elif self.type == "feature-splitter":
            return "feature-splitter.png"
        elif self.type == "feature-tunnel":
            return "feature-tunnel.png"
        elif self.type == "instr-arrow":
            return self.color + "-arrow_" + self.ARROW_DIRS[
                self.arrow_dir] + ".png"
        elif self.type == "instr-bond":
            if self.choice == 0:
                variant = "_plus"
            elif self.choice == 1:
                variant = "_minus"
            return self.color + "-bond" + variant + ".png"
        elif self.type == "instr-control":
            if self.choice == 0:
                variant = "a"
            elif self.choice == 1:
                variant = "b"
            elif self.choice == 2:
                variant = "c"
            elif self.choice == 3:
                variant = "d"
            return self.color + "-control_" + variant + "_" + self.ARROW_DIRS[
                self.arrow_dir] + ".png"
        elif self.type == "instr-debug":
            return self.color + "-debug.png"
        elif self.type == "instr-fuse":
            return self.color + "-fuse.png"
        elif self.type == "instr-grab":
            if self.choice == 0:
                variant = "grab_drop"
            elif self.choice == 1:
                variant = "grab"
            elif self.choice == 2:
                variant = "drop"
            return self.color + "-" + variant + ".png"
        elif self.type == "instr-input":
            if self.choice == 0:
                variant = "1"
            elif self.choice == 1:
                variant = "2"
            return self.color + "-in_" + variant + ".png"
        elif self.type == "instr-output":
            if self.choice == 0:
                variant = "1"
            elif self.choice == 1:
                variant = "2"
            return self.color + "-out_" + variant + ".png"
        elif self.type == "instr-rotate":
            if self.choice == 0:
                variant = "cw"
            elif self.choice == 1:
                variant = "ccw"
            return self.color + "-rotate_" + variant + ".png"
        elif self.type == "instr-sensor":
            return self.color + "-sensor_" + self.ARROW_DIRS[
                self.arrow_dir] + ".png"
        elif self.type == "instr-split":
            return self.color + "-split.png"
        elif self.type == "instr-start":
            return self.color + "-start_" + self.ARROW_DIRS[
                self.arrow_dir] + ".png"
        elif self.type == "instr-swap":
            return self.color + "-swap.png"
        elif self.type == "instr-sync":
            return self.color + "-sync.png"
        elif self.type == "instr-toggle":
            return self.color + "-toggle_" + self.ARROW_DIRS[
                self.arrow_dir] + ".png"
class SolutionRank(db.Model):
    __tablename__ = 'solution_ranks'

    solution_id = db.Column(db.Integer,
                            db.ForeignKey('solutions.solution_id'),
                            primary_key=True)
    leaderboard_id = db.Column(db.Integer,
                               db.ForeignKey('leaderboards.leaderboard_id'),
                               primary_key=True)
    level_id = db.Column(db.Integer,
                         db.ForeignKey('levels.level_id'),
                         primary_key=True)
    reactors = db.Column(db.Integer, primary_key=True, default=0)
    rank = db.Column(db.Integer)

    solution = db.relationship('Solution',
                               backref=db.backref(
                                   'ranks',
                                   cascade='save-update, merge, delete'))
    leaderboard = db.relationship('Leaderboard',
                                  backref=db.backref(
                                      'ranks',
                                      cascade='save-update, merge, delete'))

    def __init__(self,
                 solution_id,
                 leaderboard_id,
                 level_id,
                 rank,
                 reactors=0):
        self.solution_id = solution_id
        self.leaderboard_id = leaderboard_id
        self.level_id = level_id
        self.rank = rank
        self.reactors = reactors

    @property
    def rank_str(self):
        if self.rank % 10 == 1 and self.rank % 100 != 11:
            return str(self.rank) + 'st'
        elif self.rank % 10 == 2 and self.rank % 100 != 12:
            return str(self.rank) + 'nd'
        elif self.rank % 10 == 3 and self.rank % 100 != 13:
            return str(self.rank) + 'rd'
        else:
            return str(self.rank) + 'th'

    @staticmethod
    def recalculate(level_id):
        SolutionRank.query.filter(SolutionRank.level_id == level_id).delete()

        # cycles
        # first do the "any reactors" leaderboard
        users = set()
        rank = 1

        solutions = (Solution.query.filter(
            and_(Solution.level_id == level_id,
                 Solution.approved == True)).order_by('cycle_count',
                                                      'symbol_count',
                                                      'reactor_count',
                                                      'upload_time').all())
        for solution in solutions:
            if solution.user_id not in users:
                users.add(solution.user_id)
                solution_rank = SolutionRank(solution.solution_id, 1,
                                             solution.level_id, rank)
                db.session.add(solution_rank)
                rank += 1

        # then any individual reactor counts if necessary
        if solution.level.outside_view:
            reactor_options = (db.session.query(Solution.reactor_count).filter(
                and_(Solution.level_id == level_id,
                     Solution.reactor_count != 0,
                     Solution.approved == True)).distinct().order_by(
                         Solution.reactor_count).all())
            for reactor_count in reactor_options:
                users = set()
                rank = 1

                solutions = (Solution.query.filter(
                    and_(Solution.level_id == level_id,
                         Solution.reactor_count == reactor_count[0],
                         Solution.approved == True)).order_by(
                             'cycle_count', 'symbol_count',
                             'upload_time').all())
                for solution in solutions:
                    if solution.user_id not in users:
                        users.add(solution.user_id)
                        solution_rank = SolutionRank(solution.solution_id, 1,
                                                     solution.level_id, rank,
                                                     reactor_count[0])
                        db.session.add(solution_rank)
                        rank += 1

        # symbols
        # first do the "any reactors" leaderboard
        users = set()
        rank = 1

        solutions = (Solution.query.filter(
            and_(Solution.level_id == level_id,
                 Solution.approved == True)).order_by('symbol_count',
                                                      'cycle_count',
                                                      'reactor_count',
                                                      'upload_time').all())
        for solution in solutions:
            if solution.user_id not in users:
                users.add(solution.user_id)
                solution_rank = SolutionRank(solution.solution_id, 2,
                                             solution.level_id, rank)
                db.session.add(solution_rank)
                rank += 1

        # then any individual reactor counts if necessary
        if solution.level.outside_view:
            reactor_options = (db.session.query(Solution.reactor_count).filter(
                and_(Solution.level_id == level_id,
                     Solution.reactor_count != 0)).distinct().order_by(
                         Solution.reactor_count).all())
            for reactor_count in reactor_options:
                users = set()
                rank = 1

                solutions = (Solution.query.filter(
                    and_(Solution.level_id == level_id,
                         Solution.reactor_count == reactor_count[0],
                         Solution.approved == True)).order_by(
                             'symbol_count', 'cycle_count',
                             'upload_time').all())
                for solution in solutions:
                    if solution.user_id not in users:
                        users.add(solution.user_id)
                        solution_rank = SolutionRank(solution.solution_id, 2,
                                                     solution.level_id, rank,
                                                     reactor_count[0])
                        db.session.add(solution_rank)
                        rank += 1
        db.session.commit()