""" surl - a URL shortening application, supports various sites """

# Copyright (c) 2009 Savvas Radevic <vicedar@gmail.com>
#
# 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/>.

import sys
import re
import getopt
import urllib
import urllib2

# for controlOutputMethod
import tempfile
import shutil
import os.path

#dirs.py
try:
    import dirs
except ImportError:
    import surl.dirs as dirs

#plugins.py
try:
    import plugins as ps
except ImportError:
    import surl.plugins as ps

#services.py
try:
    import services
except ImportError:
    import surl.services as services

#surlscript.py
try:
    import surlscript as scripting
except ImportError:
    import surl.surlscript as scripting

#version.py
try:
    import version as sv
except ImportError:
    import surl.version as sv

class _arguments:
    """ surl command line argument parsing and error detection """
    def __init__(self, sys_argv = []):
        self.arguments = {
            'api-key'   : '',
            'mode'      : 'stdin',
            'in-place'  : False,
            'links-only': False,
            'output'    : '',
            'password'  : '',
            'quiet'     : False,
            'service'   : 'tinyurl.com',
            'title'     : '',
            'username'  : '',
        }
        self.sys_arguments = sys_argv

    def usage(self, error = 1):
        """ Prints surl command usage """
        print("Version: " + sv.surl_version)

        service_list = services.supportedServices().keys()
        service_list.sort()
        print("Purpose: a URL shortening application, supports various sites")
        print("Modes: stdin or -f (default: stdin)")
        print("Usage: surl [OPTION]\n")
        print("  -h, --help\t\t\tThis help message")
        print("  -v, --version\t\t\tPrints version info")
        print("  -c, --url <url>\t\tURL to shorten")
        print("  -a, --api-key <key>\t\tUse an API key")
        print("  -f, --file <filename>\t\tRead input from filename")
        print("  -i, --in-place\t\tSave changes to file directly (Requires: -f)")
        print("  -l, --links-only\t\tOutput only a list of links (old_url => shortened_url)")
        print("  -o, --output <filename>\tOutput to file")
        print("  -p, --password <password>\tYour password")
        print("  -q, --quiet\t\t\tSuppress any extra messages")
        print("  -t, --title <title>\t\tTitle (short custom name/alias) for the url\n")
        print("  -u, --username <username>\tYour username\n")
        print("  -s, --service <service>\tThe URL shortening service to use (Default: tinyurl.com)")
        print("  -r, --script <path>\t\tThe surlscript to execute")
        print("\n\nSupported URL shortening services:")
        print(" %s\n" % ', '.join(service_list))
        print("Example:\n  echo 'http://google.co.uk' | surl -a myapikey123 -u example -s bit.ly")
        print("  echo 'http://www.google.com' | surl -s tr.im")
        print("  surl -c http://www.google.com -s tr.im")
        print("  surl -f example.txt -i -s tr.im")
        ps.call("Help()")
        ps.unload()
        sys.exit(error)

    def version(self, error = 1):
        print("surl " + sv.surl_version)
        ps.call("Version()")
        ps.unload()
        sys.exit(error)

    def parseArgs(self):
        """ Reads the arguments and checks if used properly """
        if re.search('surl(?:\.py)?$', self.sys_arguments[0]):
            # If surl is called without setup, strip the first argument
            sys_args = self.sys_arguments[1:]
        else:
            sys_args = self.sys_arguments

        try:
            opts, pargs = getopt.getopt(
                sys_args,
                'hvc:a:f:ilo:p:qs:t:u:r:',
                [   'help',
                    'version',
                    'url=',
                    'api-key=',
                    'file=',
                    'in-place',
                    'links-only',
                    'output=',
                    'password=',
                    'quiet',
                    'service=',
                    'title=',
                    'username=',
                    'script=',
                ]
            )
        except getopt.GetoptError, e:
            self.usage("ERROR: " + e.msg)

        # Set the mode first
        for opt, arg in opts:
            if opt in ('-h', '--help'):
                self.usage(0)

            if opt in ('-v', '--version'):
                self.version(0)

            if opt in ('-c', '--url'):
                if not arg:
                    self.usage("ERROR: %s <url>" % (opt))
                self.arguments['mode'] = 'url'
                self.arguments['url'] = arg

            if opt in ('-f', '--file'):
                if not arg:
                    self.usage("ERROR: %s <filename>" % (opt))
                self.arguments['mode'] = 'file'
                if os.path.isfile(arg):
                    self.arguments['file'] = arg
                else:
                    self.usage("ERROR: File in %s <filename> does not exist" % (opt))
            # No need to check for stdin, it's on by default

            if opt in ('-r', '--script'):
                if not arg:
                    self.usage("ERROR: %s <url>" % (opt))
                scripting.parseScript(self.arguments, arg)

        for opt, arg in opts:
            if opt in ('-a', '--api-key'):
                if not arg:
                    self.usage("ERROR: -a <api-key>")
                self.arguments['api-key'] = arg
            elif opt in ('-i', '--in-place'):
                if self.arguments['output']:
                    self.usage("ERROR: Cannot use -i (--in-place) and -o (--output) together")
                if self.arguments['mode'] == 'stdin':
                    self.usage("ERROR: -i should be used with -f <filename>")
                self.arguments['in-place'] = True
            elif opt in ('-l', '--links-only'):
                self.arguments['links-only'] = True
            elif opt in ('-o', '--output'):
                if not arg:
                    self.usage("ERROR: -o <filename>")
                if self.arguments['in-place']:
                    self.usage("ERROR: Cannot use -i (--in-place) and -o (--output) together")
                self.arguments['output'] = arg
            elif opt in ('-p', '--password'):
                if not arg:
                    self.usage("ERROR: -p <password>")
                self.arguments['password'] = arg
            elif opt in ('-q', '--quiet'):
                self.arguments['quiet'] = True
            elif opt in ('-s', '--service'):
                if not arg:
                    self.usage("ERROR: -s <servicename>")
                self.arguments['service'] = arg
            elif opt in ('-t', '--title'):
                if not arg:
                    self.usage("ERROR: -t <title>")
                self.arguments['title'] = arg
            elif opt in ('-u', '--username'):
                if not arg:
                    self.usage("ERROR: -u <username>")
                self.arguments['username'] = arg
        return self.arguments

class core:
    def getInput(self):
        """ Read input from stdin or file """
        if self.args['mode'] == 'stdin':
            #True = not piped, just terminal
            if sys.stdin.isatty():
                _arguments().usage()
            #Get sys.stdin.readlines()
            w_input = sys.stdin.read()
        elif self.args['mode'] == 'url':
            w_input = self.args['url']
        elif self.args['mode'] == 'file':
            #Get file contents
            try:
                w_input = open(self.args['file']).read()
            except IOError, e:
                ps.unload()
                sys.exit(e)
        return w_input

    def getLinksListFromInput(self, input):
        """ Return a list of detected links from input """
        url_list = re.findall(self.urlRegexString(), input)
        return url_list

    def getShortenedLinksWithoutInput(self, url_list):
        """ Reads array url_list and returns an array with tuple items:
            (original_link, shortened_link)
        """
        result_list = list()
        for url in url_list:
            result_list.append((url, self.service.get(self.args, url)))
            #item: (original_link, shortened_link)
        return result_list

    def getShortenedLinksWithInput(self, input):
        """ Returns a text string of the input with in-place changed
            shortened links.
        """
        result = re.sub(self.urlRegexString(), self.processRegexMatch, input)
        return result
    def processRegexMatch(self, match):
        """ Gets links for getShortenedLinksWithInput """
        return self.service.get(self.args, match.group(0))

    def getShortenedLink(self, service_link, url, values=dict(), headers=dict()):
        """ Return the shortened link
            If values is an empty dictionary, it will use GET instead of POST.
            Arguments:
                * service_link (e.g. example.com/shorten.php)
                * url (the original long_url)
                * values (variables and values passed to the service link,
                  encoded using urllib.encode())
                  Default: empty dictionary {}
                * headers: Add extra http headers if necessary
                  Default: empty dictionary {}
        """
        h = self.headers(headers)
        if values:
            #Use POST
            post_fields = urllib.urlencode(values)
            request = urllib2.Request(service_link, post_fields, h)
        else:
            #Use GET
            request = urllib2.Request(service_link, None, h)
        try:
            contents = urllib2.urlopen(request).read()
        except urllib2.HTTPError, e:
            #TODO: Control error output with quiet argument
            #print(str(e) + " " + url + " -- " + e.read() + " ")
            print(str(e) + " " + url)
            contents = url
        except urllib2.URLError, e:
            #Catch if service doesn't exist
            exit("ERROR: %s - %s" % (service_link, e.reason))
        contents = contents.rstrip()
        if not contents:
            return url
        return contents

    def headers(self, extra=dict()):
        """ Return headers to be used when contacting services """
        headers = {
            'User-agent': 'surl/' + sv.surl_version,
        }
        result = dict(headers.items() + extra.items())
        return result

    def runService(self, args):
        """ Main function, requires arguments """
        dictcmd = services.supportedServices()
        #setup args
        self.args = args
        #input from file, argument or stdin
        self.input = self.getInput()
        #setup service
        try:
            self.service = dictcmd[args['service']]
        except KeyError, e:
            _arguments().usage("ERROR: No such service: " + str(e))

        self.controlOutputStyle()

    def controlOutputStyle(self):
        """ Controls output style (--links-only or not) """
        result = ''
        if self.args['links-only']:
            url_list = self.getLinksListFromInput(self.input)
            list = self.getShortenedLinksWithoutInput(url_list)
            for long_url, short_url in list:
                # long.example.com/of/a/url.html => short.com
                result = result + long_url + ' => ' + short_url + "\n"
        else:
            #full text output with shortened links
            if self.args['mode'] == 'url':
                result = self.getShortenedLinksWithInput(self.input + "\n")[0:len(result) - 1]
            elif self.args['mode'] == 'stdin':
                result = self.getShortenedLinksWithInput(self.input)
                result = result.rstrip()
            else:
                result = self.getShortenedLinksWithInput(self.input)
        self.controlOutputMethod(result)

    def copyTempFileToDest(self, result, dest):
        """ Make a temporary file and then copy it to the defined destination """
        t = tempfile.mkstemp()[1]
        f = open(t, 'w')
        f.write(result)
        f.close()
        shutil.copyfile(t, dest)

    def controlOutputMethod(self, result):
        """ Controls which output method to use (e.g. -o <file>) """
        ps.call("PreOutput()")
        
        if self.args['in-place']:
            # Write to file from -f / --file
            self.copyTempFileToDest(result, self.args['file'])

        elif self.args['output']:
            # Write to file from -o / --output
            self.copyTempFileToDest(result, self.args['output'])

        else:
            # Print to stdout
            print result
        
        ps.call("PostOutput(\"" + result + "\")")
        ps.unload()

    def urlRegexString(self):
        """ Returns the regular expressions string used for catching URLs """
        urlregex = {
            'protocol': '(?:(?:https?|ftp)://)?',
            'byip'    : '\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}',
            'bydomain': '\S+\.(?:com|org|net|gov|mil|biz|info|mobi|name|aero|jobs|museum|travel|[a-z]{2})',
            'rest2'   : '(?:/\S*|/?(?=\s))',
        }
        urlregex_string = urlregex['protocol'] + \
            '(?:' + \
                urlregex['byip'] + '|' + urlregex['bydomain'] + \
            ')' + \
            urlregex['rest2']
        return urlregex_string

def main():
    plist = ps.read_plugin_lst() # read plugin.lst
    ps.load(plist) # load plugins
    args = _arguments(sys.argv).parseArgs() # parse arguments
    core().runService(args)

if __name__ == '__main__':
    main()
