#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# @author starenka
# @contact admin@starenka.net
# @depends python-sqlite,sqlite3,[xclip]
import sqlite3,os,sys,string
from optparse import OptionParser

PATH = os.path.expanduser("~")+'/' # /home/user
DB_FILE = PATH+'shell-fu.db'  # file name
NOTICE_COLOR = '\033[94m'
ERROR_COLOR = '\033[91m'
RESET_COLOR = "\x1B[0m"
ODD_COLOR = '\033[92m'
EVEN_COLOR = '\033[90m'

class Output:
    def notice(self,text):
        print NOTICE_COLOR+'\n(*): '+text+RESET_COLOR
    def err(self,text):
        print ERROR_COLOR+'\n(!): '+text+RESET_COLOR
    def odd(self,text,line = False):
        if(line): print ODD_COLOR+text+RESET_COLOR
        else: return ODD_COLOR+text+RESET_COLOR 
    def even(self,text,line = False):
        if(line): print EVEN_COLOR+text+RESET_COLOR
        else: return EVEN_COLOR+text+RESET_COLOR 

class Clipboard:
    def paste2klipper(self,data):
        try: os.system('dcop klipper klipper setClipboardContents '+self.escape(data))
        except: Output().err('Error copying data to klipper. Are you sure you are running KDE?')
    def paste2xclip(self,data): 
        try: os.system('echo "'+self.escape(data,False)+'" | xclip -i < /dev/stdin')
        except os.error: Output().err('Error copying data to xclip. Are you sure you have packages installed?')
    def escape(self,data,wrap = True):
        data = data.replace('"','\\"')
        if wrap: return '"'+data.replace('`','\\`')+'"'
        else: return data.replace('`','\\`')

class Tag:
    def add(self,data):
        db.cursor.execute('INSERT INTO tag VALUES (NULL,?)',[data])
        db.commit()
        db.cursor.execute('SELECT MAX(tag_id) FROM tag')
        return db.cursor.fetchone()[0]
    def get_id(self,tag_name):
        db.cursor.execute('SELECT tag_id FROM tag WHERE tag_name = ?',[tag_name])
        id = db.cursor.fetchone()
        if id is not None: return id[0]
        else: return id 
    def make_item_link(self,item_id,tag):        
        tag_id = self.get_id(tag)
        if tag_id is None: tag_id = self.add(tag)
        db.cursor.execute('INSERT INTO item_has_tag VALUES (?,?)',(item_id,tag_id))
        db.commit()
    def get_all(self):
        db.cursor.execute('SELECT tag_name FROM tag')
        return db.cursor.fetchall()
    def delete_orphaned(self):
        db.cursor.execute('DELETE FROM tag WHERE tag_id NOT IN (SELECT tag_id FROM item_has_tag)')
        db.commit()
        
class Item:
    def add(self,data,tags):
        db.cursor.execute('INSERT INTO item VALUES (NULL,?)',[data])
        db.commit()
        db.cursor.execute('SELECT MAX(item_id) FROM item')
        id = db.cursor.fetchone()
        for tag in tags:
            Tag().make_item_link(id[0],tag.strip())
    def get_all_by_tag(self,tag):
        db.cursor.execute('''SELECT DISTINCT item_content,i.item_id FROM item AS i JOIN item_has_tag AS ihs
        ON i.item_id = ihs.item_id JOIN tag AS t ON ihs.tag_id = t.tag_id
        WHERE tag_name LIKE ?''',['%'+tag+'%'])
        return db.cursor.fetchall()
    def get_all(self):
        db.cursor.execute('SELECT item_content,item_id FROM item')
        return db.cursor.fetchall()        
    def delete(self,item_id):
        db.cursor.execute('DELETE FROM item WHERE item_id = ?',[item_id])
        db.cursor.execute('DELETE FROM item_has_tag WHERE item_id = ?',[item_id])
        Tag().delete_orphaned()
        db.commit()
    def is_unique(self,item):
        db.cursor.execute('SELECT item_id FROM item WHERE item_content = ?',[item])
        id = db.cursor.fetchone()
        if id is None: return True
        else: return False 

class Storage:
    def __init__(self):
        self.link = sqlite3.connect(DB_FILE)
        self.cursor = self.link.cursor()  
        self.create()
    def close(self):
        self.cursor.close()
    def commit(self):
        self.link.commit()
        self.cursor.close()
    def create(self):      
        try:
            self.cursor.execute('CREATE TABLE tag (tag_id INTEGER PRIMARY KEY, tag_name TEXT )')
            self.cursor.execute('CREATE TABLE item (item_id INTEGER PRIMARY KEY, item_content TEXT)')
            self.cursor.execute('CREATE TABLE item_has_tag (item_id INTEGER, tag_id INTEGER)')
            Output().notice('No database found. Creating '+DB_FILE)
        except sqlite3.OperationalError, e:
            pass
        self.commit()


usage = "\n\tshell-fu [tag] [-a] [-s \"tag\"] [-l] [-t]"
parser = OptionParser(usage)
parser.add_option('-a','--add',action='store_true',dest='add',default = False,\
                  help='add new item')
parser.add_option('-s','--search',action='store',dest='search',default = False,\
                  help='searches items tagged by ...')
parser.add_option('-l','--list',action='store_true',dest='list',default = False,\
                  help='list all items')
parser.add_option('-t','--tags',action='store_true',dest='tags',default = False,\
                  help='list all tags')
(options,args) = parser.parse_args()    

db = Storage()
if options.add:
    item = raw_input('Item content: ')
    while item == '': item = raw_input('Item content: ')
    if Item().is_unique(item):
        tags = raw_input('Tag it (separate tags w/ comma): ')
        while tags == '': tags = raw_input('Tag it (separate tags w/ comma): ')
        tags = tags.split(',')
        Item().add(item, tags)
        Output().notice('Item added')
    else: Output().err('Item already exists')
    sys.exit()

if options.tags:
    out = '\n'
    c = 0
    for tag in Tag().get_all():
        if c%2==0: out+= Output().even(tag[0]+' ')
        else: out+= Output().odd(tag[0]+' ')
        c+=1
    print out+'\n'
    sys.exit()

if options.search is not False or len(sys.argv)==2 or options.list is not False:
    if options.search is False: tag = sys.argv[1]
    else: tag = options.search
    if options.list is False: items = Item().get_all_by_tag(tag)
    else: items = Item().get_all()
    if len(items) > 0:
        c = 0
        for item in items:
            if c%2==0: Output().even('['+str(c+1)+'] '+item[0],True)
            else: Output().odd('['+str(c+1)+'] '+item[0],True)
            c+=1
        input = raw_input('Use item # (e for exit): ')
        if input.lower() == 'e': sys.exit()
        else:
            while int(input) > len(items) or int(input)<1:
                input = raw_input('Use item # (e for exit): ')
            action = raw_input('Paste to clipboard - (k)lipper, (x)clip | (d)elete item: ')
            if action.lower() == 'd':  
                Item().delete(items[int(input)-1][1])
                Output().notice('Item removed')
            if action.lower() == 'k': Clipboard().paste2klipper(items[int(input)-1][0])
            if action.lower() == 'x': Clipboard().paste2xclip(items[int(input)-1][0])
    else: Output().err('No items found')