" TODO:
" - The technique doesn't work with unnamed files, vim refuses to allow :w
"   command.
" - Lots of testing
" - Filename completion for Note
" - Better name generation for automatic names
" - Tree view and note management
" - Plugginization
"
let g:notesRoot = 'c:/tmp/root' " Please, no trailing-slash for now.
let g:notesDefaultName = 'New Note' " Add .txt automatically.
let g:notesMaxNameLenth = 100
let g:notesSyncNameAndTitle = 1

if !isdirectory(g:notesRoot)
    echohl ErrorMsg | echomsg "Notes: Configured root doesn't exist or is not a directory: " .
		\ g:notesRoot | echohl NONE
endif

command! -nargs=? Note :call OpenNote('<args>')
function! OpenNote(notePath)
    if a:notePath == ''
	let notePath = NewName(g:notesRoot, g:notesDefaultName)
    else
	if (len(filter(['win16', 'win32', 'win65', 'win95', 'dos16', 'dos32'], 'has(v:val)')) > 0 &&
		    \ a:notePath =~ '^\([a-zA-Z]:\|[/\\]\)') || a:notePath =~ '^/'
	    " This is an absolute path
	    if fnamemodify(a:notePath, ':h') != fnamemodify(g:notesRoot)
		echohl ErrorMsg | echohl 'Note not under g:notesRoot' | echohl None
		return
	    endif
	    let notePath = a:notePath
	else
	    let notePath = fnamemodify(g:notesRoot.'/'.a:notePath, ':p')
	endif
    endif
    exec 'sp' notePath
    call InstallAutoCmd()
endfunction

function! InstallAutoCmd()
    aug NoteSync
	au BufWriteCmd <buffer> :call feedkeys(":SyncNoteName!\<CR>", 'n')
    aug END
endfunction

function! UninstallAutoCmd()
    aug NoteSync
	au! BufWriteCmd <buffer>
    aug END
endfunction

command! -bang SyncNoteName :call SyncNoteName(expand('<bang>') == '!' ? 1 : 0)
" Look at the first non-empty line and sync the filename to it.
function! SyncNoteName(save) abort
    if &modified
	if !a:save
	    echohl ErrorMsg | echo 'Note currently modified, save it first' | echohl NONE
	    return
	endif
	try
	    call UninstallAutoCmd()
	    write!
	catch
	    echohl ErrorMsg | echo 'Error saving the file: '.v:exception | echohl NONE
	    return
	finally
	    call InstallAutoCmd()
	endtry
    endif
    if !g:notesSyncNameAndTitle
	return
    endif

    let goodLine = ''
    for lineNr in range(1, line('$'))
	let line = getline(lineNr) 
	if line =~ '\w'
	    let goodLine = line
	    break
	endif
    endfor
    if goodLine != ''
	let name = substitute(line, '[^[:alnum:]_ ]', '_', 'g')[: g:notesMaxNameLenth-1]
	let newNotePath = NewName(g:notesRoot, name)
	let prevNotePath = expand('%:p')
	if NoteRootName(newNotePath) == NoteRootName(prevNotePath)
	    return
	endif
	if writefile(map(getline(1, '$'), '&ff == "dos" ? v:val."\<CR>" : v:val'), newNotePath) == 0
	    " Success copying the contents, we can now switch to the new
	    " note and remove the other note
	    try
		exec 'edit' newNotePath
		call InstallAutoCmd()
	    catch
		if expand('%:p') == fnamemodify(prevNotePath, ':p')
		    " We are still in the previous note, it is safe to remove
		    " the intermediate file.
		    if delete(newNotePath) != 0
			echohl ErrorMsg |
				    \ echomsg 'An unexpected error prevented sync and '.
				    \ 'deleting the intermediate file also failed, manual '.
				    \ 'removal is required for: '.newNotePath."\nOriginal error: ".
				    \ v:exception |
				    \ echohl NONE
		    endif
		    if expand('#:p') == fnamemodify(newNotePath, ':p')
			bw #
		    endif
		else
		    echohl ErrorMsg |
				\ echomsg 'Aborted sync on an unxpected error, old path: '.
				\ prevNotePath. ' new path:'.newNotePath |
				\ echohl NONE
		endif
	    endtry
	    if delete(prevNotePath) != 0
		echohl ErrorMsg | echomsg 'Error removing the old path to the note: '.prevNotePath | echohl NONE
	    else
		silent! bw #
	    endif
	endif
    endif
endfunction

" Creates a new note and returns the path.
" This should really be the job of tempname(), but it doesn't accept the
" directory and prefix (like the Java's utility does)
" LIMITATIONS:
" - Has to start with "1" for the first file for the logic to work, will be
"   nice if number is inserted only from "2" onwards.
function! NewName(dir, prefix)
    let curSuffixMax = max(sort(map(map(
		\ split(glob(a:dir.'/'.a:prefix.'*.txt'), "\<NL>"),
		\   'substitute(v:val, "^.*\\(\\d\\+\\)\\s*\\.txt$", "\\1", "")'),
		\ 'v:val + 0'), 'NumCompare'))
    let newNamePath = a:dir . '/' . a:prefix . ' '.(curSuffixMax + 1) . '.txt'
    " Just make sure we will not accidentally overwrite an existing file.
    if exists(newNamePath)
	throw "Notes:Couldn't create a new note, found an existing note with the same name: " . newNamePath
    endif
    if writefile([], newNamePath) != 0
	throw "Notes:writefile() failed to create new note: " . newNamePath
    endif
    return newNamePath
endfunction

function! NumCompare(i1, i2)
    return a:i1 == a:i2 ? 0 : a:i1 > a:i2 ? 1 : -1
endfunc

function! NoteRootName(noteName)
    let noteName = fnamemodify(a:noteName, ':t:r')
    return substitute(noteName, '\d\+\s*$', '', '')
endfunction
