#!/usr/bin/env python
from os.path import expanduser
from subprocess import call
import curses
import locale
import math
import ntpath
import os
import re
import subprocess
import sys
import time

searchTerm = ""
updateText = ""
showPreview = True
selectedItem = -1
documentSet = []
results = []
storedException = None
EDITOR = os.environ.get('EDITOR','vim') #that easy!
notationalDir = os.getcwd()

# Set up curses to interpret non-ASCII characters
locale.setlocale(locale.LC_ALL, '')
code = locale.getpreferredencoding()

""" Setup cursors screen
Set up screen with no echo settings etc.
"""
def setupScreen():
    global screen
    screen = curses.initscr()
    curses.noecho()
    curses.curs_set(0)
    screen.keypad(1)

def sorted_ls(path):
    mtime = lambda f: os.stat(os.path.join(path, f)).st_mtime
    return reversed(list(sorted(os.listdir(path), key=mtime)))

""" Update keyword search
Read all the files in the notational folder and update the interal document list
to reflect their contents. Note: for lots of files this could cause a hang.
"""
def updateDocumentSet():
    global documentSet
    documentSet =  []
    for note in sorted_ls(notationalDir):
        if os.path.isfile(note):
            filePath = notationalDir + '/' + note
            with open(filePath, 'r') as f:
                words = []
                for line in f:
                    line = re.sub(r'[^a-zA-Z0-9]',' ', line) # remove non-alphanumeric
                    line = line.lower() # make all lowercase
                    for word in line.split():
                        words.append(word)
                documentSet.append((filePath, set(words)))

""" Search for a term in files
Using the internally generated document set, search for any files that contain
the term, or have the term in the header. Returns the files as a list of paths.
"""
def findFiles(term):
    files = []
    for document in documentSet:
        # print(document[1])
        basename = os.path.splitext(ntpath.basename(document[0]))[0].lower()
        if any(term.lower() in s for s in document[1]) or term.lower() in basename:
            files.append(document[0])
    return files

""" Create full width string,
Will add spaces between texta and textb in order to make it fill entire screen width
then will return
"""
def fullWidthString(texta, textb):
    h,w = screen.getmaxyx()
    space = w - len(texta) - len(textb) -1
    return texta + ' ' * space + textb

""" Safely print in curses passing errors
"""
def safePrint(text, formatting = None):
    try:
        if formatting == None:
            screen.addstr(text)
        else:
            screen.addstr(text, formatting)
    except curses.error:
        pass

""" Print file preview
Print the first @number of files from @filename, note that this
doesn't take into account line wrapping """
def printFile(filename, number = None):
    with open(filename, 'r') as f:
        h,w = screen.getmaxyx()
        counter = 0
        lastline = ''
        for line in f:
            counter += math.ceil(len(line)/w)
            safePrint(line)
            lastline = line
            if counter >= number:
                break
        # add newline if missing
        if '\n' not in lastline:
            safePrint('\n')
        # print blank lines
        while counter < number-1:
            safePrint('~\n')
            counter += 1

""" Main page drawing function
Clears the screen and updates it with the current search term at the top
as well as a list of notes that contain the searched term. It also highlights
the currently selected file for editing.
"""
def drawPage():
    global selectedItem
    global results

    h,w = screen.getmaxyx()
    screen.clear()

    # Print current search term
    screen.addstr(fullWidthString("Search Term: %s" % searchTerm, updateText) + '\n', 
            curses.A_REVERSE)
    results = findFiles(searchTerm)

    if showPreview and selectedItem >= 0:
        maxh = int(h-(h/1.8))
    else:
        maxh = h-1

    maxh = len(results) if len(results) < maxh else maxh
    if selectedItem >= maxh: 
        selectedItem = maxh-1

    elif selectedItem < -1:
        selectedItem = -1

    # print each item in results, highlight selected item
    for x in range(maxh):
        # only show file title
        lineItem = fullWidthString(os.path.splitext(ntpath.basename(results[x]))[0],
            time.ctime(os.path.getmtime(results[x])))
        if x == selectedItem:
            safePrint(lineItem + '\n', curses.A_REVERSE)
        else:
            safePrint(lineItem + '\n')

    if showPreview and selectedItem >= 0:
        safePrint(w * '-')
        printNumLines = h - maxh -1
        printFile(results[selectedItem], printNumLines)

""" Note editing function
If a file is selected it will open it in your favourite editor, otherwise it'll
create a new file with the searchterm as its name and open that.
"""
def editPage():
    global results
    global selectedFile
    curses.endwin() # close curses app
    if selectedItem < 0: # if new file
        selectedFile = notationalDir + '/' + searchTerm + '.txt'
        if not os.path.isfile(selectedFile):
            open (selectedFile, 'a').close()
    else: #otherwise use selected file
        selectedFile = results[selectedItem]

    with open(selectedFile, 'r+') as tempfile:
      tempfile.flush()
      call([EDITOR, tempfile.name])
      # do the parsing with `tempfile`
    setupScreen()
    updateDocumentSet()
    drawPage()

def deleteSelectedFile():
    global updateText
    if selectedItem >= 0:
        os.remove(results[selectedItem])
        updateText = 'deleted \'%s\'' % os.path.splitext(ntpath.basename(results[selectedItem]))[0]
    else:
        updateText = 'No note selected, can\'t delete'

if __name__ == "__main__":
    # If argument given use that as notational directory, otherwise just use
    # current directory
    if len(sys.argv) > 1:
        print(sys.argv[1])
        if not os.path.isdir(expanduser(sys.argv[1])):
            print("That doesn't seem to be a directory...")
            print("python notational.py [notaional directory]")
            sys.exit(1)
        notationalDir = expanduser(sys.argv[1])


    # Initial page draw
    setupScreen()
    updateDocumentSet()
    drawPage()

    while True:
        try:
            if storedException:
                break
            event = screen.getch()
            if event == 27:
                screen.nodelay(True)
                n = screen.getch()
                if n == -1:
                    break
                elif n == 112: # alt + p: toggle preview
                    if showPreview:
                        showPreview = False
                        updateText = 'Hide preview'
                    else:
                        updateText = 'Show preview'
                        showPreview = True
                    drawPage()
                elif n == 113: # alt + q: quit
                    break
                elif n == 114: # alt + r: reload files
                    searchTerm = ''
                    updateText = 'Documents updated!'
                    updateDocumentSet()
                    drawPage()
                elif n == 100: # alt + d: delete file
                    deleteSelectedFile()
                    updateDocumentSet()
                    drawPage()
                screen.nodelay(False)
            elif event == curses.KEY_BACKSPACE or event == 127: # backspace
                searchTerm = searchTerm[:-1]
                drawPage()
            elif event == curses.KEY_UP: # up
                selectedItem = selectedItem - 1
                drawPage()
            elif event == curses.KEY_DOWN: # down
                selectedItem = selectedItem + 1
                drawPage()
            elif event == 10:
                editPage()
                drawPage()
            elif event < 257:
                updateText = ''
                searchTerm = searchTerm + str(chr(event))
                drawPage()
        except KeyboardInterrupt:
            storedException = sys.exc_info()
    curses.endwin()
    print 'Thanks for using QuickNote!'
    print '-- Linus'
    sys.exit();
