#!/usr/bin/env python
# -*- coding: utf-8

import os
import datetime
import bcrypt
import binascii
import math
import psycopg2
from flask import Flask, request, session, g, redirect, url_for, abort, render_template, flash
from flask_sqlalchemy import SQLAlchemy
from functools import wraps


########################################################################
# App settings
########################################################################
app = Flask(__name__)
app.config.from_pyfile('config.py')
app.secret_key = '446307a5f61c2bb810436b2ee0f903f2'
app.debug = False
app.static_url_path='/static'
db = SQLAlchemy(app)

########################################################################
# Database
########################################################################
class Stock_users(db.Model):
  __tablename__ = 'stock_users'
  id = db.Column(db.Integer, primary_key=True)
  mail = db.Column(db.Text, nullable=False)
  password = db.Column(db.Text, nullable=False)
  name = db.Column(db.Text, nullable=False)

class Stock_componants(db.Model):
  __tablename__ = 'stock_componants'
  id = db.Column(db.Integer, primary_key=True)
  reference = db.Column(db.Text, nullable=False)
  designation = db.Column(db.Text, nullable=False)
  last_price = db.Column(db.Float)
  mean_price = db.Column(db.Float)
  quantity = db.Column(db.Integer)
  min_quantity = db.Column(db.Integer)
  place = db.Column(db.Text, nullable=False)
  provider_id = db.Column(db.Integer, nullable=False)

class Stock_providers(db.Model):
  __tablename__ = 'stock_providers'
  id = db.Column(db.Integer, primary_key=True)
  name = db.Column(db.Text, nullable=False)
  address = db.Column(db.Text)
  mail = db.Column(db.Text)
  url = db.Column(db.Text)
  comment = db.Column(db.Text)

class Stock_kits(db.Model):
  __tablename__ = 'stock_kits'
  id = db.Column(db.Integer, primary_key=True)
  name = db.Column(db.Text, nullable=False)
  designation = db.Column(db.Text, nullable=False)

class Stock_kit_compositions(db.Model):
  __tablename__ = 'stock_kit_compositions'
  id = db.Column(db.Integer, primary_key=True)
  kit_id = db.Column(db.Integer, db.ForeignKey('stock_kits.id'), nullable=False)
  componant_id = db.Column(db.Integer, db.ForeignKey('stock_componants.id'), nullable=False)
  quantity = db.Column(db.Integer, nullable=False)

class Stock_orders(db.Model):
  __tablename__ = 'stock_orders'
  id = db.Column(db.Integer, primary_key=True)
  componant_id = db.Column(db.Integer, db.ForeignKey('stock_componants.id'))
  componant_quantity = db.Column(db.Integer, nullable=False)
  date = db.Column(db.DateTime, nullable=False, default=datetime.datetime.utcnow)
  injected = db.Column(db.Boolean, nullable=False, default=False)

########################################################################
# Here comes the fun
########################################################################
def get_max_kit(kit_id):
  """ Get max available kit from kit.id """
  default_max_kit = float("inf")
  max_kit = default_max_kit
  composition = Stock_kit_compositions.query.filter_by(kit_id=kit_id).with_entities(
                                                      Stock_kit_compositions.componant_id,
                                                      Stock_kit_compositions.quantity).all()
  for c_componant in composition:
    componant = Stock_componants.query.filter_by(id=c_componant.componant_id).first()
    if componant:
      try:
        mkit = int(componant.quantity) / int(c_componant.quantity)
      except ZeroDivisionError as e:
        print "[+] Error at get_max_kit:"
        print "------------------------------"
        print "%s" % e.message
        print "------------------------------"        
      if mkit < max_kit:
        max_kit = mkit
  if max_kit == default_max_kit:
    max_kit = 0
  return max_kit

def sync_cookies(response, session):
  """ Sync cookies with session """
  for key in session:
    response.set_cookie(key, value=str(session[key]))
    #~ if key != u'session':
      #~ print '[c]', key, session[key]

def sync_session(request, session):
  """ Sync session with cookies"""
  for key in request.cookies:
    try:
      old = str(session[key])
      session[key] = int(str(request.cookies[key].encode('utf8')))
    except ValueError:
      # Value is not an int, will be treated as string
      session[key] = str(request.cookies[key].encode('utf8'))
    except KeyError:
      # Key does not exist in session
      try:
        session[key] = int(str(request.cookies[key].encode('utf8')))
      except ValueError:
        # Value is not an int, will be treated as string
        session[key] = str(request.cookies[key].encode('utf8'))
    #~ if key != u'session':
      #~ print '[s]', key, request.cookies[key]

def check_user(request, session):
  """ Check user credentials """
  if session['token'] and request.cookies['token']:
    if len(session['token']) > 0 and len(request.cookies['token']) > 0:
      if session['token'] == request.cookies['token']:
        return True
      return False
  if 'login' not in request.cookies or 'password' not in request.cookies:
    return False
  if request.cookies['login'] and request.cookies['password']:
    if request.cookies['login'] > 0 and request.cookies['password'] > 0:
      hashed = Stock_users.query.filter_by(mail=request.cookies['login']).with_entities(Stock_users.password).first()
      if hashed is None:
        # User is unknown
        return False
      password = request.cookies['password'].encode('utf8')
      hashed = hashed[0].encode('utf8')
      if bcrypt.checkpw(password, hashed):
        session['password'] = ''
        session['login'] = ''
        session['token'] = binascii.hexlify(os.urandom(42))
        return True
    # Password mismatch
    return False

def resume_session(func):
  """ Resume pending session """
  @wraps(func)
  def check(*args, **kwargs):
    # A motherfuckin' bunch of defaults values
    css='neutral'
    empty=u''
    limit = 12
    offset = 0
    page = 1
    nexthop = offset + limit
    prevhop = offset
    order = u'asc'
    order_refresh = 0
    c_sort = u'reference'
    c_provider = 0
    c_count = 0
    p_sort = u'name'
    p_count = 0
    k_sort = u'name'
    k_count = 0
    kc_quantity = 0
    kc_limit = 3
    if not u'css' in session:
      session[u'css'] = css
    if not u'token' in session:
      session[u'token'] = empty
    if not u'password' in session:
      session[u'password'] = empty
    if not u'login' in session:
      session[u'login'] = empty
    if not u'session' in session:
      session[u'session'] = empty
    if not u'c_limit' in session:
      session[u'c_limit'] = limit
    if not u'c_offest' in session:
      session[u'c_offset'] = offset
    if not u'c_sort' in session:
      session['c_sort'] = c_sort
    if not u'c_order' in session:
      session[u'c_order'] = order
    if not u'c_order_refresh' in session:
      session[u'c_order_refresh'] = order_refresh
    if not u'c_page' in session:
      session[u'c_page'] = page
    if not u'c_nexthop' in session:
      session[u'c_nexthop'] = nexthop
    if not u'c_prevhop' in session:
      session['c_prevhop'] = prevhop
    if not u'c_reference' in session:
      session[u'c_reference'] = empty
    if not u'c_designation' in session:
      session[u'c_designation'] = empty
    if not u'c_place' in session:
      session[u'c_place'] = empty
    if not u'c_provider' in session:
      session[u'c_provider'] = c_provider
    if not u'c_count' in session:
      session[u'c_count'] = c_count
    if not u'p_sort' in session:
      session[u'p_sort'] = p_sort
    if not u'p_order' in session:
      session[u'p_order'] = order
    if not u'p_order_refresh' in session:
      session[u'p_order_refresh'] = order_refresh
    if not u'p_page' in session:
      session[u'p_page'] = page
    if not u'p_nexthop' in session:
      session[u'p_nexthop'] = nexthop
    if not u'p_prevhop' in session:
      session[u'p_prevhop'] = prevhop
    if not u'p_offset' in session:
      session[u'p_offset'] = offset
    if not u'p_limit' in session:
      session[u'p_limit'] = limit
    if not u'p_name' in session:
      session[u'p_name'] = empty
    if not u'p_address' in session:
      session[u'p_address'] = empty
    if not u'p_mail' in session:
      session[u'p_mail'] = empty
    if not u'p_url' in session:
      session[u'p_url'] = empty
    if not u'p_comment' in session:
      session[u'p_comment'] = empty
    if not u'p_count' in session:
      session['p_count'] = p_count
    if not u'k_sort' in session:
      session[u'k_sort'] = k_sort
    if not u'k_order' in session:
      session[u'k_order'] = order
    if not u'k_order_refresh' in session:
      session[u'k_order_refresh'] = order_refresh
    if not u'k_page' in session:
      session[u'k_page'] = page
    if not u'k_nexthop' in session:
      session[u'k_nexthop'] = nexthop
    if not u'k_prevhop' in session:
      session[u'k_prevhop'] = prevhop
    if not u'k_offset' in session:
      session[u'k_offset'] = offset
    if not u'k_limit' in session:
      session[u'k_limit'] = limit
    if not u'k_name' in session:
      session[u'k_name'] = empty
    if not u'k_address' in session:
      session[u'k_address'] = empty
    if not u'k_mail' in session:
      session[u'k_mail'] = empty
    if not u'k_url' in session:
      session[u'k_url'] = empty
    if not u'k_designation' in session:
      session[u'k_designation'] = empty
    if not u'k_count' in session:
      session[u'k_count'] = k_count
    if not u'kc_limit' in session:
      session[u'kc_limit'] = kc_limit
    if not u'kc_componant_id' in session:
      session[u'kc_componant_id'] = empty
    if not u'kc_quantity' in session:
      session[u'kc_quantity'] = kc_quantity
    if not u'kc_designation' in session:
      session[u'kc_designation'] = empty
    if not u'kc_reference' in session:
      session[u'kc_reference'] = empty
    # Cookies/session sync
    sync_session(request, session)
    # Switch sort order
    refresh = {u'desc': u'asc', u'asc': u'desc'}
    if session[u'c_order_refresh'] == 1:
      session[u'c_order'] = refresh[session[u'c_order']]
      session[u'c_order_refresh'] = 0
    if session[u'p_order_refresh'] == 1:
      session[u'p_order'] = refresh[session[u'p_order']]
      session[u'p_order_refresh'] = 0
    if session[u'k_order_refresh'] == 1:
      session[u'k_order'] = refresh[session[u'k_order']]
      session[u'k_order_refresh'] = 0
    # Check for valid session
    if not check_user(request, session):
      # User is not logged in, send him back to login page
      return render_template('login.html', css=session[u'css'])
    # Everything's fine
    return func(*args, **kwargs)
  return check

########################################################################
# Routes
########################################################################
@app.errorhandler(404)
@resume_session
def page_not_found(e):
  """ 404 not found """
  return render_template('error.html', css=session[u'css']), 404

@app.route("/", methods=['GET', 'POST'])
@resume_session
def authenticate():
  """ Friend or foo ? """
  response = app.make_response(render_template('index.html', css=session[u'css']))
  sync_cookies(response, session)
  return response


########################################################################
# Componants
########################################################################

@app.route('/componants', methods=['GET', 'POST'])
@resume_session
def componants():
  """ Componants list """
  providers = Stock_providers.query.order_by(Stock_providers.id).all()
  return render_template('componants.html',
                            providers=providers,
                            reference=session[u'c_reference'].decode('utf8'),
                            designation=session[u'c_designation'].decode('utf8'),
                            place=session[u'c_place'].decode('utf8'),
                            provider_id=session[u'c_provider'],
                            css=session[u'css'])

@app.route('/componants/<componant_id>', methods=['GET', 'POST'])
@resume_session
def get_componant(componant_id):
  """ Edit componant """
  try:
    componant_id = int(componant_id)
  except ValueError as e:
    return render_template('error.html', css=session[u'css']), 404
  componant = Stock_componants.query.filter_by(id=componant_id).first()
  if componant:
    providers = Stock_providers.query.order_by(Stock_providers.name).all()
    provider = componant.provider_id
    provider = Stock_providers.query.filter_by(id=provider).first()
    return render_template('componant.html',
                            componant=componant,
                            providers=providers,
                            provider=provider,
                            css=session[u'css'])
  return render_template('error.html', css=session[u'css']), 404

@app.route('/componants/update/<componant_id>', methods=['POST'])
@resume_session
def update_componant(componant_id):
  """ Update componant field"""
  field = request.form['field']
  value = request.form['value']
  if field and value:
    try:
      componant = Stock_componants.query.filter_by(id=componant_id).first()
      setattr(componant, field, value)
      commit =  db.session.commit()
      if commit == None:
        return 'OK'
    except Exception as e:
      pass
  return 'KO'

@app.route('/componants/delete/<componant_id>', methods=['GET', 'POST'])
@resume_session
def delete_componant(componant_id):
  """ Delete componant """
  try:
    componant_id = int(componant_id)
    Stock_componants.query.filter_by(id=componant_id).delete()
    db.session.commit()
  except ValueError as e:
    return render_template('error.html', css=session[u'css']), 404
  except Exception as e:
    db.session.rollback()
    print "[+] Error at delete_componant:"
    print "------------------------------"
    print "%s" % e.message
    print "------------------------------"
  return componants()

@app.route('/componants/new', methods=['POST'])
@resume_session
def new_componant():
  """ Add componant """
  componant = Stock_componants(reference=session[u'c_reference'].decode('utf8'),
                               designation=session[u'c_designation'].decode('utf8'),
                               last_price=0,
                               mean_price=0,
                               quantity=0,
                               min_quantity=0,
                               place=session[u'c_place'].decode('utf8'),
                               provider_id=session[u'c_provider'])
  try:
    db.session.add(componant)
    commit =  db.session.commit()
  except Exception as e:
    db.session.rollback()
    print "[+] Error at new_componant:"
    print "------------------------------"
    print "%s" % e.message
    print "------------------------------"
    return 'KO'
  if commit != None:
    return 'KO'
  return 'OK'

## Componants update result set
@app.route('/componants/update', methods=['GET', 'POST'])
@resume_session
def update_componants():
  """ Display componants list """
  # search by reference
  like = '%s%s%s' % ('%', str(session[u'c_reference']), '%')
  componants = Stock_componants.query.filter(Stock_componants.reference.like(like))
  # search by designation
  like = '%s%s%s' % ('%', str(session[u'c_designation']), '%')
  componants = componants.filter(Stock_componants.designation.like(like))
  # search by place
  like = '%s%s%s' % ('%', str(session[u'c_place']),'%')
  componants = componants.filter(Stock_componants.place.like(like))
  # search by provider
  if session['c_provider'] > 1:
    componants = componants.filter_by(provider_id=session[u'c_provider'])
  # Pages calculation
  session[u'c_count'] = componants.count()
  session[u'c_pagecount'] = int(math.ceil(session[u'c_count'] / float(session[u'c_limit'])))
  session[u'c_page'] = int(math.ceil(float(float(session[u'c_offset']) + 1) / float(session[u'c_limit'])))
  if session[u'c_page'] > session[u'c_pagecount']:
    session[u'c_page'] = session[u'c_pagecount']
    session[u'c_offset'] = 0
  session[u'c_nexthop'] = session[u'c_offset'] + session[u'c_limit']
  if session[u'c_nexthop'] > session[u'c_count'] - 1:
    session[u'c_nexthop'] = int(session[u'c_offset'])
  session[u'c_prevhop'] =  int(session[u'c_offset'])  - session[u'c_limit']
  if session[u'c_prevhop'] < 0:
    session[u'c_prevhop']  = 0
  # Sorting
  sort = getattr(Stock_componants, session[u'c_sort'])
  if session[u'c_order'] == u'desc':
    sort = getattr(Stock_componants, session[u'c_sort']).desc()
  componants =   componants.order_by(sort)
  # Applying offset
  componants = componants.offset(session[u'c_offset'])
  # Applying limit
  componants = componants.limit(session[u'c_limit'])
  # Get result
  componants = componants.all()
  
  response = app.make_response(render_template('result_componants.html',
                            componants=componants,
                            offset=session[u'c_offset'] ,
                            nexthop=session[u'c_nexthop'],
                            prevhop=session[u'c_prevhop'],
                            page_count=session[u'c_pagecount'],
                            page=session[u'c_page'],
                            sort=session[u'c_sort'].decode('utf8'),
                            order=session[u'c_order'].decode('utf8'),
                            row_count=session[u'c_count']))
  sync_cookies(response, session)
  return response

########################################################################
# Orders
########################################################################
def compute_mean_price(componant_id, price, quantity):
  """ Compute mean price """
  try:
    componant_id = int(componant_id)
    price = float(price)
    quantity = float(quantity)
  except ValueError:
    # bad parameters
    return -1
  componant = Stock_componants.query.filter_by(id=componant_id).first()
  if componant:
    actual_mean_price = componant.mean_price
    actual_quantity = componant.quantity
    actual_total = actual_mean_price * actual_quantity
    order_total = price * quantity
    grand_total = order_total + actual_total
    new_mean_price = grand_total / (actual_quantity + quantity)
    return new_mean_price
  # Componant does not exist
  return -1

@app.route('/orders/in/<componant_id>')
@resume_session
def in_componants(componant_id):
  """ Incoming order """
  return render_template('wip.html', css=session[u'css'])

@app.route('/orders/out/<componant_id>')
@resume_session
def out_componants(componant_id):
  """ Outgoing order """
  return render_template('wip.html', css=session[u'css'])


########################################################################
# Providers
########################################################################
@app.route('/providers', methods=['GET', 'POST'])
@resume_session
def providers():
  """ Providers list """
  return render_template('providers.html',
                            name=session[u'p_name'].decode('utf8'),
                            address=session[u'p_address'].decode('utf8'),
                            mail=session[u'p_mail'].decode('utf8'),
                            url=session[u'p_url'].decode('utf8'),
                            comment=session[u'p_comment'].decode('utf8'),
                            css=session[u'css'])

@app.route('/providers/<provider_id>')
@resume_session
def get_provider(provider_id):
  """ Edit provider """
  try:
    provider_id = int(provider_id)
  except ValueError as e:
    return render_template('error.html', css=session[u'css']), 404
  provider = Stock_providers.query.filter_by(id=provider_id).first()
  if provider:
    return render_template('provider.html', provider=provider, css=session[u'css'])
  return render_template('error.html', css=session[u'css']), 404


@app.route('/providers/update/<provider_id>', methods=['POST'])
@resume_session
def update_provider(provider_id):
  """ Update provider field"""
  field = request.form['field']
  value = request.form['value']
  if field and value:
    try:
      provider = Stock_providers.query.filter_by(id=provider_id).first()
      setattr(provider, field, value)
      commit =  db.session.commit()
      if commit == None:
        return 'OK'
    except Exception as e:
      pass
  return 'KO'

@app.route('/providers/new', methods=['POST'])
@resume_session
def new_provider():
  """ Add provider """
  provider = Stock_providers(name=session[u'p_name'].decode('utf8'),
                            address=session[u'p_address'].decode('utf8'),
                            mail=session[u'p_mail'].decode('utf8'),
                            url=session[u'p_url'].decode('utf8'),
                            comment=session[u'p_comment'].decode('utf8'))
  try:
    db.session.add(provider)
    commit =  db.session.commit()
  except Exception as e:
    db.session.rollback()
    print "[+] Error at new_provider:"
    print "------------------------------"
    print "%s" % e.message
    print "------------------------------"
    return 'KO'
  if commit != None:
    return 'KO'
  return 'OK'

@app.route('/providers/delete/<provider_id>')
@resume_session
def delete_provider(provider_id):
  """ Delete provider """
  try:
    provider_id = int(provider_id)
    Stock_providers.query.filter_by(id=provider_id).delete()
    db.session.commit()
  except ValueError as e:
    return render_template('error.html', css=session[u'css']), 404
  except Exception as e:
    db.session.rollback()
    print "[+] Error at delete_provider:"
    print "------------------------------"
    print "%s" % e.message
    print "------------------------------"
  return providers()

@app.route('/providers/update', methods=['POST'])
@resume_session
def search_providers():
  """ Display componants list """
  # search by reference
  like = '%s%s%s' % ('%', str(session[u'p_name']), '%')
  providers = Stock_providers.query.filter(Stock_providers.name.like(like))
  # search by address
  like = '%s%s%s' % ('%', str(session[u'p_address']), '%')
  providers = providers.filter(Stock_providers.address.like(like))
  # search by place
  like = '%s%s%s' % ('%', str(session[u'p_mail']),'%')
  providers = providers.filter(Stock_providers.mail.like(like))
  # search by place
  like = '%s%s%s' % ('%', str(session[u'p_url']),'%')
  providers = providers.filter(Stock_providers.url.like(like))
  # search by place
  like = '%s%s%s' % ('%', str(session[u'p_comment']),'%')
  providers = providers.filter(Stock_providers.comment.like(like))
  # Don't take 'all' and 'none' entry
  providers = providers.filter(Stock_providers.id > 2)

  # Pages calculation
  session[u'p_count'] = providers.count()
  session[u'p_pagecount'] = int(math.ceil(session[u'p_count'] / float(session[u'p_limit'])))
  session[u'p_page'] = int(math.ceil(float(float(session[u'p_offset']) + 1) / float(session[u'p_limit'])))
  if session[u'p_page'] > session[u'p_pagecount']:
    session[u'p_page'] = session[u'p_pagecount']
    session[u'p_offset'] = 0
  session[u'p_nexthop'] = session[u'p_offset'] + session[u'p_limit']
  if session[u'p_nexthop'] > session[u'p_count'] - 1:
    session[u'p_nexthop'] = int(session[u'p_offset'])
  session[u'p_prevhop'] =  int(session[u'p_offset']) - session[u'p_limit']
  if session[u'p_prevhop'] < 0:
    session[u'p_prevhop']  = 0
  # Sorting
  sort = getattr(Stock_providers, session[u'p_sort'])
  if session[u'p_order'] == u'desc':
    sort = getattr(Stock_providers, session[u'p_sort']).desc()
  providers =   providers.order_by(sort)
  # Applying offset
  providers = providers.offset(session[u'p_offset'])
  # Applying limit
  providers = providers.limit(session[u'p_limit'])
  # Get result
  providers = providers.all()
  response = app.make_response(render_template('result_providers.html',
                            providers=providers,
                            offset=session[u'p_offset'] ,
                            nexthop=session[u'p_nexthop'],
                            prevhop=session[u'p_prevhop'],
                            page_count=session[u'p_pagecount'],
                            page=session[u'p_page'],
                            sort=session[u'p_sort'].decode('utf8'),
                            order=session[u'p_order'].decode('utf8'),
                            row_count=session[u'p_count']))
  sync_cookies(response, session)
  return response


########################################################################
# Kits
########################################################################
@app.route('/kits', methods=['GET', 'POST'])
@resume_session
def kits():
  return render_template('kits.html',
                            name=session[u'k_name'].decode('utf8'),
                            designation=session[u'k_designation'].decode('utf8'),
                            css=session[u'css'])

@app.route('/kits/<kit_id>')
@resume_session
def get_kit(kit_id):
  """ Edit kit """
  try:
    kit_id = int(kit_id)
  except ValueError as e:
    return render_template('error.html', css=session[u'css']), 404
  kit = Stock_kits.query.filter_by(id=kit_id).first()
  if kit:
    return render_template('kit.html', kit=kit, css=session[u'css'])
  return render_template('error.html', css=session[u'css']), 404

@app.route('/kits/update/<kit_id>', methods=['POST'])
@resume_session
def update_kit(kit_id):
  """ Update kit field"""
  field = request.form['field']
  value = request.form['value']
  if field and value:
    try:
      kit = Stock_kits.query.filter_by(id=kit_id).first()
      setattr(kit, field, value)
      commit =  db.session.commit()
      if commit == None:
        return 'OK'
    except Exception as e:
      pass
  return 'KO'

@app.route('/kits/new', methods=['POST'])
@resume_session
def new_kit():
  """ Add kit """
  kit = Stock_kits(name=session[u'k_name'].decode('utf8'),
                    designation=session[u'k_designation'].decode('utf8'))
  try:
    db.session.add(kit)
    commit =  db.session.commit()
  except Exception as e:
    db.session.rollback()
    print "[+] Error at new_kit:"
    print "------------------------------"
    print "%s" % e.message
    print "------------------------------"
    return 'KO'
  if commit != None:
    return 'KO'
  return 'OK'

@app.route('/kits/delete/<kit_id>')
@resume_session
def delete_kit(kit_id):
  """ Delete kit """
  try:
    kit_id = int(kit_id)
    Stock_kit_compositions.query.filter_by(kit_id=kit_id).delete()
    Stock_kits.query.filter_by(id=kit_id).delete()
    db.session.commit()
  except ValueError as e:
    return render_template('error.html', css=session[u'css']), 404
  except Exception as e:
    db.session.rollback()
    print "[+] Error at delete_kit:"
    print "------------------------------"
    print "%s" % e.message
    print "------------------------------"
  return kits()

@app.route('/kits/update', methods=['POST'])
@resume_session
def search_kits():
  """ Display componants list """
  # search by name
  like = '%s%s%s' % ('%', str(session[u'k_name']), '%')
  kits = Stock_kits.query.filter(Stock_kits.name.like(like))
  # search by designation
  like = '%s%s%s' % ('%', str(session[u'k_designation']), '%')
  kits = kits.filter(Stock_kits.designation.like(like))

  # Pages calculation
  session[u'k_count'] = kits.count()
  session[u'k_pagecount'] = int(math.ceil(session[u'k_count'] / float(session[u'k_limit'])))
  session[u'k_page'] = int(math.ceil(float(float(session[u'k_offset']) + 1) / float(session[u'k_limit'])))
  if session[u'k_page'] > session[u'k_pagecount']:
    session[u'k_page'] = session[u'k_pagecount']
    session[u'k_offset'] = 0
  session[u'k_nexthop'] = session[u'k_offset'] + session[u'k_limit']
  if session[u'k_nexthop'] > session[u'k_count'] - 1:
    session[u'k_nexthop'] = int(session[u'k_offset'])
  session[u'k_prevhop'] =  int(session[u'k_offset']) - session[u'k_limit']
  if session[u'k_prevhop'] < 0:
    session[u'k_prevhop']  = 0
  # Sorting
  sort = getattr(Stock_kits, session[u'k_sort'].decode('utf8'))
  if session[u'k_order'] == 'desc':
    sort = getattr(Stock_kits, session[u'k_sort'].decode('utf8')).desc()
  kits =   kits.order_by(sort)
  # Applying offset
  kits = kits.offset(session[u'k_offset'])
  # Applying limit
  kits = kits.limit(session[u'k_limit'])
  # Get result
  kits = kits.all()
  for kit in kits:
    max_kit = get_max_kit(kit.id)
    setattr(kit, 'max_kit', max_kit)
  response = app.make_response(render_template('result_kits.html',
                            kits=kits,
                            offset=session[u'k_offset'] ,
                            nexthop=session[u'k_nexthop'],
                            prevhop=session[u'k_prevhop'],
                            page_count=session[u'k_pagecount'],
                            page=session[u'k_page'],
                            sort=session[u'k_sort'].decode('utf8'),
                            order=session[u'k_order'].decode('utf8'),
                            row_count=session[u'k_count']))
  for key in session:
    response.set_cookie(key, value=str(session[key]))
    if key != 'session':
      print '[c]', key, session[key]
  return response

@app.route('/kits/composition/<kit_id>', methods=['POST'])
@resume_session
def get_kit_composition(kit_id):
  kit_composition = []
  composition = Stock_kit_compositions.query.filter_by(kit_id=kit_id).with_entities(
                                                      Stock_kit_compositions.componant_id,
                                                      Stock_kit_compositions.quantity).all()
  # FIXME: Use join rather than this ugly work around
  # FIXME: Good luck !
  for c_componant in composition:
    componant = Stock_componants.query.filter_by(id=c_componant.componant_id).first()
    if componant:
      c = {u'id': componant.id,
           u'reference': componant.reference,
           u'designation': componant.designation,
           u'quantity': componant.quantity,
           u'needed': c_componant.quantity
           }
      kit_composition.append(c)
  max_kit = get_max_kit(kit_id)
  return render_template('kit_composition.html', kit_composition=kit_composition, max_kit=max_kit, kit_id=kit_id)

@app.route('/kits/remove/<kit_id>/<componant_id>', methods=['GET'])
@resume_session
def remove_componant_from_kit(kit_id, componant_id):
  """ Remove componant from kit """
  try:
    kit_id = int(kit_id)
    Stock_kit_compositions.query.filter_by(kit_id=kit_id).filter_by(componant_id=componant_id).delete()
    db.session.commit()
  except ValueError as e:
    return render_template('error.html', css=session[u'css']), 404
  except Exception as e:
    db.session.rollback()
    print "[+] Error at remove_componant_from_kit:"
    print "------------------------------"
    print "%s" % e.message
    print "------------------------------"
  return get_kit(kit_id)

@app.route('/kits/composition/add/<kit_id>', methods=['POST', 'GET'])
@resume_session
def add_componant_to_kit(kit_id):
  """ Add componant to kit """
  try:
    kit_id = int(kit_id)
    count = Stock_kit_compositions.query.filter_by(kit_id=kit_id, componant_id=session[u'kc_componant_id']).count()
    if count > 0:
      # Componant already in kit composition
      # => Updating with new quantity
      return update_kit_composition(kit_id)

    composition = Stock_kit_compositions(kit_id=kit_id, 
                                          componant_id=session[u'kc_componant_id'],
                                          quantity=session[u'kc_quantity'])
    db.session.add(composition)
    commit =  db.session.commit()
  except ValueError as e:
    return render_template('error.html', css=session[u'css']), 404
  except Exception as e:
    db.session.rollback()
    print "[+] Error at add_componant_to_kit:"
    print "------------------------------"
    print "%s" % e.message
    print "------------------------------"
    return 'KO'
  if commit != None:
    return 'KO'
  return 'OK'

@app.route('/kits/composition/update/<kit_id>', methods=['GET', 'POST'])
@resume_session
def update_kit_composition(kit_id):
  """ Update componant quantity """
  try:
    kit_id = int(kit_id)
    composition = Stock_kit_compositions.query.filter_by(
                        kit_id=kit_id, componant_id=session[u'kc_componant_id']).first()
    composition.quantity = session[u'kc_quantity']
    db.session.commit()
  except ValueError as e:
    return render_template('error.html', css=session[u'css']), 404
  except Exception as e:
    db.session.rollback()
    print "[+] Error at update_kit_composition:"
    print "------------------------------"
    print "%s" % e.message
    print "------------------------------"
  return get_kit(kit_id)

## Componants update result set
@app.route('/kits/componants/update/<kit_id>', methods=['POST'])
@resume_session
def update_kit_componants(kit_id):
  """ Display componants list """
  # search by reference
  like = '%s%s%s' % ('%', str(session[u'kc_reference']), '%')
  componants = Stock_componants.query.filter(Stock_componants.reference.like(like))
  # search by designation
  like = '%s%s%s' % ('%', str(session[u'kc_designation']), '%')
  componants = componants.filter(Stock_componants.designation.like(like))
  # Applying limit
  componants = componants.limit(session[u'kc_limit'])
  # Get result
  componants = componants.all()
  response = app.make_response(render_template('result_kit_componants.html', kit_id=kit_id, componants=componants))
  sync_cookies(response, session)
  return response

# Main #######################################

if __name__ == '__main__':
  app.run(host='0.0.0.0')