#!/usr/bin/env python

# spud - keep track of photos
# Copyright (C) 2008-2013 Brian May
#
# 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/>.
from __future__ import absolute_import
from __future__ import unicode_literals
from __future__ import print_function

from six.moves import configparser
import datetime
import pytz
import argparse
import os.path
import beanbag.v2 as beanbag

from beanbag.v2 import Request
from beanbag.bbexcept import BeanBagException
from requests import Session

from spud import media


class NotFoundError(Exception):
    pass


def GET(*args, **kwargs):
    try:
        return beanbag.GET(*args, **kwargs)
    except BeanBagException as e:
        if e.response.status_code == 404:
            raise NotFoundError(e.msg)
        raise


def POST(*args, **kwargs):
    print(args, kwargs)
    try:
        return beanbag.POST(*args, **kwargs)
    except BeanBagException as e:
        if e.response.status_code == 404:
            raise NotFoundError(e.msg)
        raise


referer = None


class BeanBag(beanbag.BeanBag):
    pass


class CSRFBeanBag(BeanBag):

    def make_request(self, path, verb, req):
        global referer

        if verb not in ["GET", "HEAD", "TRACE", "OPTIONS"]:
            cookies = self.session.cookies
            if 'csrftoken' in cookies:
                req.headers["X-CSRFToken"] = cookies['csrftoken']
            req.headers["referer"] = referer
        return super(~CSRFBeanBag, self).make_request(path, verb, req)


def get_albums(spud, albums):
    results = []
    for album in albums:
        if '/' in album:
            parent, _, create_album = album.rpartition("/")
        else:
            parent = album
            create_album = None

        result = get_album(spud, parent, create_album)
        results.append(result)

    return results


def get_album(spud, parent, create_album):
    try:
        parent_result = GET(spud.albums(obj=parent))
    except NotFoundError:
        raise RuntimeError("Cannot find album '%s'" % parent)

    assert parent_result["count"] == 1
    assert parent_result is not None
    parent_result = parent_result["results"][0]

    if create_album is not None:
        try:
            result = GET(spud.albums(
                obj=create_album,
                parent=parent_result["id"],
            ))

            assert result["count"] == 1
            result = result["results"][0]

        except NotFoundError:
            result = POST(
                spud.albums,
                {
                    'title': create_album,
                    'parent': parent_result["id"],
                    'sort_name': 'date',
                    'sort_order': create_album,
                })
            print("Created album %s" % result)
    else:
        result = parent_result

    return result["id"]


def get_categorys(spud, categorys):
    results = []
    for category in categorys:
        try:
            result = GET(spud.categorys(obj=category))
        except NotFoundError:
            raise RuntimeError("Cannot find category '%s'" % category)

        assert result["count"] == 1
        results.append(result["results"][0]["id"])
    return results


def get_place(spud, place):
    try:
        result = GET(spud.places(obj=place))
    except NotFoundError:
        raise RuntimeError("Cannot find place '%s'" % place)

    assert result["count"] == 1
    return result["results"][0]["id"]


def get_person(spud, person):
    try:
        result = GET(spud.persons(obj=person))
    except NotFoundError:
        raise RuntimeError("Cannot find person '%s'" % person)

    assert result["count"] == 1
    return result["results"][0]["id"]


def get_persons(spud, persons):
    results = []
    for person in persons:
        if person == "unknown":
            results.append("unknown")
            continue

        results.append(get_person(spud, person))
    return results


def parse_timezone(value):
    if value[0] == "+" or value[0] == "-":
        sign, offset = (value[0], value[1:])
        if len(offset) == 2:
            offset = int(offset) * 60
        elif len(offset) == 4:
            offset = (
                int(offset[0:2]) * 60 +
                int(offset[2:4]))
        else:
            raise RuntimeError("can't parse timezone %s" % value)
        if sign == '-':
            offset = -offset
        timezone = pytz.FixedOffset(offset)
        offset = None

    else:
        try:
            timezone = pytz.timezone(value)
        except pytz.UnknownTimeZoneError:
            raise RuntimeError("unknown timezone %s" % value)

    return timezone


def parse_timedelta(time):
    positive = 1
    if time[0] == "-":
        time = time[1:]
        positive = -1

    hh, mm, ss = time.split(":")
    hh = int(hh)
    mm = int(mm)
    ss = int(ss)

    return datetime.timedelta(seconds=positive*(hh*3600+mm*60+ss))


def import_photo(
        config, spud, src_filename,
        photographer, place, albums, categorys,
        src_timezone, dst_timezone, offset, dry_run):

    print()

    # check source file
    if not os.path.exists(src_filename):
        raise RuntimeError("source photo doesn't exist at %s" % (src_filename))

    m = media.get_media(src_filename)
    try:
        print("rotating %s..." % src_filename)
        m.rotate("auto")
    except NotImplementedError:
        pass

    print("processing %s..." % src_filename)
    exif = m.get_normalized_exif()

    # set everything without commiting anything
    photo = {}
    photo['photographer_pk'] = photographer
    photo['places_pk'] = place
    photo['albums_pk'] = albums
    photo['categorys_pk'] = categorys
    photo['level'] = 1

    camera = None
    if 'camera_model' in exif:
        camera = exif['camera_model']

    # get time
    dt = m.get_datetime()
    print("src time %s" % dt)

    if dt is None:
        dt = datetime.datetime.fromtimestamp(os.path.getmtime(src_filename))
        print("src time from file %s" % dt)

    if src_timezone is None and camera is not None \
            and config.has_section(camera):
        name = config.get(camera, "timezone")
        if name is not None:
            src_timezone = parse_timezone(name)

    if src_timezone is None:
        name = config.get('defaults', 'timezone')
        src_timezone = parse_timezone(name)

    dt = src_timezone.localize(dt)
    print("src time with timezone %s (%s)" % (dt, src_timezone))

    # adjust time for destination timezone
    if dst_timezone is None:
        name = config.get('defaults', 'timezone')
        dst_timezone = parse_timezone(name)

    dt = dt.astimezone(dst_timezone)
    print("adjusted with dst timezone %s (%s)" % (dt, dst_timezone))

    # add manual offsets
    td = datetime.timedelta(minutes=offset)
    dt += td
    print("adjusted with offset %s (%s)" % (dt, td))

    if camera is not None and config.has_section(camera):
        td = parse_timedelta(config.get(camera, 'offset'))
        dt += td
        print("adjusted with camera offset %s (%s)" % (dt, td))

    # save the time
    photo['utc_offset'] = int(dt.utcoffset().total_seconds() / 60)
    photo['datetime'] = dt.astimezone(pytz.utc).replace(tzinfo=None)

    if dry_run:
        print("would import %s"
              % (src_filename))
        return

    print("uploading photo %s" % photo)

    photo = POST(
        spud.photos,
        Request(
            data=photo,
            files={'photo': open(src_filename, 'rb')}
        )
    )

    print("imported  %s as %d"
          % (src_filename, photo['id']))

    return photo


def main():
    parser = argparse.ArgumentParser(
        description='Upload photos to spud')
    parser.add_argument(
        '--album', action="append", default=[],
        help='Add photos to album. If album1/album2 syntax, '
        'will create album1 under album2.')
    parser.add_argument(
        '--album_prefix',
        help='Create album dirname under prefix.')
    parser.add_argument(
        '--category', action="append", default=[],
        help='Add photos to category')
    parser.add_argument(
        '--place',
        help='Add photos to place')
    parser.add_argument(
        '--photographer',
        help='Add photographer to photos.')
    # parser.add_argument(
    #     '--person', action="append", default=[],
    #     help='Add person to photos')
    parser.add_argument(
        '--config',
        default=os.path.join(os.getenv('HOME'), '.spud.conf'),
        help='Configuration file')
    parser.add_argument(
        '--dry_run', action="store_true",
        help='Dry run')
    parser.add_argument(
        'files', nargs='+',
        help='Files to add')
    args = parser.parse_args()

    with open(args.config):
        pass

    config = configparser.RawConfigParser()
    config.read(args.config)

    username = config.get("spud", "username")
    password = config.get("spud", "password")
    url = config.get("spud", "url")

    global referer
    referer = url

    session = Session()
    spud = CSRFBeanBag(url, session=session, ext="/")

    try:
        print("")
        print("login")
        print("-----")
        result = POST(
            spud.session.login,
            {'username': username, 'password': password, 'no_csrf': True})
        print(result)

        print("")
        print("get_session")
        print("-----------")
        session = GET(spud.session)
        print(session)

        print("")
        print("get_albums")
        print("----------")
        albums = get_albums(spud, args.album)
        print(albums)

        print("")
        print("get_categorys")
        print("-------------")
        categorys = get_categorys(spud, args.category)
        print(categorys)

        place = None
        if args.place:
            print("")
            print("get_place")
            print("---------")
            place = get_place(spud, args.place)
            print(place)

        # print("")
        # print("get_persons")
        # print("------------")
        # persons = get_persons(spud, args.person)
        # print(persons)

        photographer = None
        if args.photographer:
            print("")
            print("get_photographer")
            print("------------")
            photographer = get_person(spud, args.photographer)
            print(photographer)

        # print("")
        # print("get_photos")
        # print("---------")
        # photos = spud.photos.GET()
        # print(photos)
        # return

        src_timezone = None
        dst_timezone = None
        offset = 0

        for filename in args.files:
            albums_copy = albums

            if args.album_prefix:

                dirname = os.path.dirname(filename)
                dirname = os.path.basename(dirname)

                print("")
                print("get_album")
                print("---------")
                print("%s/%s" % (args.album_prefix, dirname))
                album_id = get_album(spud, args.album_prefix, dirname)
                print(album_id)

                albums_copy = list(albums)
                albums_copy.append(album_id)

            import_photo(
                config, spud, filename,
                photographer, place, albums_copy, categorys,
                src_timezone, dst_timezone, offset, args.dry_run)

        print("")
        print("logout")
        print("------")
        result = POST(spud.session.logout)
        print(result)

    except BeanBagException as e:
        json = e.response.json()
        if json is not None:
            if 'detail' in json:
                print("Server error: %s" % json['detail'])
            else:
                print("Server error: %s" % json)
            exit(1)
        else:
            print("Server error:", e.msg)
            # print("Error:", e.msg.content)
        raise

    return

if __name__ == "__main__":
    main()
