#!/usr/bin/python

# md - a simple maildir command line user agent
# Copyright (C) 2010  Nic Ferrier <nic@ferrier.me.uk>

# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

"""
md - A maildir command line user agent.
"""

__author__ = "Nic Ferrier <nic@ferrier.me.uk>"
__version__ = 0.1

import os.path
import os
import sys
from mdlib import *

## This should be redefined as an option and a thread local
## Default value should come from an env var or be ~/Maildir
HOMEMAILDIR = "~/Maildir"
MAILDIR = os.path.expanduser(os.environ.get("MAILDIR", HOMEMAILDIR))

# Depends on cmdlin
import cmdln


class MdCLI(cmdln.Cmdln):
    name = "md"

    def get_optparser(self):
        """Override to allow specification of the maildir"""
        p = cmdln.Cmdln.get_optparser(self)
        p.add_option(
            "-M",
            "--maildir",
            action="store",
            dest="maildir"
            )
        p.add_option(
            "-V",
            "--verbose",
            action="store_true",
            dest="verbose"
            )
        return p

    @property
    def maildir(self):
        return getattr(self.options, "maildir", MAILDIR) or MAILDIR

    def do_lsfolders(self, subcmd, opts):
        """${cmd_name}: list the sub folders of the maildir

        ${cmd_usage}
        """
        client = MdClient(self.maildir)
        client.lsfolders(stream=sys.stdout)

    @cmdln.option("-r", "--reverse", help="reverse the listing", action="store_true")
    @cmdln.option(
        "-s", 
        "--since", 
        help="""only list the mails since this timestamp""", 
        action="store",
        default=-1
        )
    def do_ls(self, subcmd, opts, folder=""):
        """${cmd_name}: list messages in the specified folder

        ${cmd_usage}
        ${cmd_option_list}
        SINCE can be used with epoch times, for example: 

          md ls -s $(date '+%s')
        """
        client = MdClient(self.maildir)
        client.ls(
            foldername=folder, 
            stream=sys.stdout, 
            reverse=getattr(opts, "reverse", False),
            since=float(getattr(opts, "since", -1))
            )

    @cmdln.option("-r", "--reverse", help="reverse the listing", action="store_true")
    @cmdln.option(
        "-s", 
        "--since", 
        help="""only list the mails since this timestamp""", 
        action="store",
        default=-1
        )
    def do_lisp(self, subcmd, opts, folder=""):
        """${cmd_name}: list messages in the specified folder in JSON format

        ${cmd_usage}
        """
        client = MdClient(self.maildir)
        client.lisp(
            foldername=folder,
            stream=sys.stdout, 
            reverse=getattr(opts, "reverse", False),
            since=float(getattr(opts, "since", -1))
            )

    def do_make(self, subcmd, opts, path):
        """${cmd_name}: make a maildir at the specified path.

        ${cmd_usage}

        If the path is relative then create under MAILDIR
        else create at the absolute location.
        """
        d = path if path[0] == "/" else joinpath(self.maildir, path)
        os.makedirs(joinpath(d, "cur"))
        os.makedirs(joinpath(d, "new"))
        os.makedirs(joinpath(d, "tmp"))
        os.makedirs(joinpath(d, "store"))

    def do_rm(self, subcmd, opts, message):
        """${cmd_name}: remove the specified message

        ${cmd_usage}
        """
        client = MdClient(self.maildir)
        client.remove(message)

    def do_text(self, subcmd, opts, message):
        """${cmd_name}: get the best text part of the specified message

        ${cmd_usage}
        """
        client = MdClient(self.maildir)
        client.gettext(message, sys.stdout)

    def do_struct(self, subcmd, opts, message):
        """${cmd_name}: get the structure of the specified message

        ${cmd_usage}
        """
        client = MdClient(self.maildir)
        client.getstruct(message, sys.stdout)

    def do_file(self, subcmd, opts, message):
        """${cmd_name}: download the whole file of the message.
        
        ${cmd_usage}
        """
        client = MdClient(self.maildir)
        client.get(message, sys.stdout)

    def do_shell(self, subcmd, opts):
        """${cmd_name}: run a shell for md.

        ${cmd_usage}

        The MAILDIR cannot be set with this command except through the
        environment variable.
        """
        # TODO fix this because it's broken right now
        shell = MdCLI()
        mdcli.main(argv=[], loop=cmdln.LOOP_ALWAYS)

    @cmdln.option("-N", "--noop", help="do not pull", action="store_true")
    def do_pull(self, subcmd, opts, remote_maildir):
        """${cmd_name}: pull the remote maildir into the local maildir.

        ${cmd_usage}
        ${cmd_option_list}

        The REMOTE_MAILDIR is a url which specifies where the dir
        is. A few different forms are supported:

          ssh://user@hostname/path   is a remote directory at path, accessed via ssh
          file://path                is a local maildir directory at path
        """
        import mdlib.pull 
        m = re.match(
            "(?P<protocol>[a-z]+)://(?P<urlpart>.*)",
            remote_maildir
            )
        if not m:
            print >>sys.stderr, "md pull: the remote maildir url was unrecognized"
            return

        local_maildir = self.maildir
        noop = getattr(opts, "noop", False) or False
        verbose = getattr(self.options, "verbose", False) or False

        data = m.groupdict()
        if data.get("protocol") == "ssh":
            data = re.match(
                "(?P<user>[a-zA-Z0-9-]+@)*(?P<hostname>[a-zA-Z0-9.-]+)(?P<path>[a-zA-Z0-9./-]+)",
                data.get("urlpart")
                )
            host = data.get("hostname") \
                if not data.get("user") \
                else "%s%s" % (data.get("user"), data.get("hostname"))
            remote_maildir = data.get("path")
            mdlib.pull.sshpull(host, remote_maildir, local_maildir, noop, verbose)
        elif data.get("protocol") == "file":
            maildir = data.get("urlpart")
            mdlib.pull.filepull(maildir, local_maildir, noop, verbose)
        else:
            print "%s not a recognized protocol" % protocol

    def do_storecheck(self, subcmd, opts):
        """${cmd_name}: checks the store for files that may not be in the maildirs.
        """
        from os.path import basename
        from os.path import dirname
        from os.path import exists as existspath
        from os.path import islink
        from os.path import join as joinpath
        maildir = self.maildir
        cur = joinpath(maildir, "cur")
        new = joinpath(maildir, "new")
        store = joinpath(maildir, "store")
        
        found_list = []
        # Loop through the folders checking that everything maps back to the store
        for scandir in [cur, new]:
            for f in os.listdir(scandir):
                filename = joinpath(scandir, f)
                try:
                    assert islink(filename)
                    store_location = os.readlink(filename)
                    assert existspath(store_location) and dirname(store_location) == store
                except AssertionError:
                    print "%s was not a link into the store" % (
                        "/".join([
                                filename.split("/")[-2],
                                filename.split("/")[-1]
                                ])
                        )
                else:
                    found_list.append(basename(store_location))

        for storefile in os.listdir(store):
            if storefile not in found_list:
                print "%s found in store but not folders" % joinpath("store", storefile)


if __name__ == "__main__":
    try:
        mdcli = MdCLI()
        sys.exit(mdcli.main())
    except KeyboardInterrupt:
        pass

# End
