from gevent import monkey
monkey.patch_all()

import sys
import os
import optparse
import logging
from datetime import datetime
from flask import Flask, request, Response, abort, jsonify, \
    render_template, session
from flask.helpers import send_file
from djrandom import daemonize
from djrandom import utils
from djrandom.model.mp3 import MP3, PlayLog
from djrandom.model.playlist import Playlist
from djrandom.model.external import AlbumImageRetriever
from djrandom.database import Session, init_db
from djrandom.frontend import search
from sqlalchemy import distinct


log = logging.getLogger(__name__)
app = Flask(__name__)
app.secret_key = 'J@9als[13- "!>0@!!zWz}='
storage_root = None
searcher = None
album_images = None

USERID_COOKIE = 'USERID'


@app.before_request
def check_session_before_request():
    # Recover the user id from the long-term cookie, if present,
    # otherwise just generate a new one.
    if 'userid' not in session:
        if USERID_COOKIE in request.cookies:
            session['userid'] = request.cookies[USERID_COOKIE]
        else:
            session['userid'] = utils.random_token()
            session['_please_save_userid'] = True
            log.info('created new userid: %s' % session['userid'])


@app.after_request
def save_session_after_request(resp):
    if session.get('_please_save_userid'):
        del session['_please_save_userid']
        resp.set_cookie(USERID_COOKIE, value=session['userid'],
                        expires=(2<<30) - 1)
    return resp


@app.teardown_request
def shutdown_dbsession(exception=None):
    Session.remove()


@app.route('/json/artists')
def all_artists_json():
    artists = [x[0] for x in Session.query(distinct(MP3.artist)) if x[0]]
    return jsonify(artists=artists)


@app.route('/json/albums/<artist>')
def artist_albums_json(artist):
    albums = [x[0] for x in 
              Session.query(distinct(MP3.album)).filter_by(artist=artist)]
    return jsonify(albums=albums)


@app.route('/json/album/<artist>/<album>')
def album_songs_json(artist, album):
    songs = [x.to_dict() for x in MP3.query.filter_by(
            error=False, ready=True, artist=artist, album=album)]
    return jsonify(songs=songs)


@app.route('/json/song/<sha1>')
def song_info_json(sha1):
    mp3 = MP3.query.get(sha1)
    if not mp3:
        abort(404)
    return jsonify(mp3.to_dict())


@app.route('/json/playing/<sha1>')
def play_callback(sha1):
    mp3 = MP3.query.get(sha1)
    if not mp3:
        abort(404)
    # Increase play count.
    if mp3.play_count:
        mp3.play_count += 1
    else:
        mp3.play_count = 1
    # Add a playlog entry.
    plog = PlayLog(sha1=sha1, userid=session['userid'],
                   stamp=datetime.now())
    Session.add(plog)
    Session.add(mp3)
    Session.commit()
    return jsonify(status=True)


@app.route('/album_image/<artist>/<album>')
def get_album_image(artist, album):
    img_file = album_images.get_album_image(
        urllib.urlunquote_plus(artist),
        urllib.urlunquote_plus(album))
    if not img_file:
        abort(404)
    return send_file(img_file, mimetype='image/jpeg', conditional=True)


@app.route('/json/playlist/save', methods=['POST'])
def save_playlist():
    hashes = request.form.get('h', '')
    uuid = request.form.get('uuid')
    if uuid == 'null':
        uuid = None
    playlist = None
    if uuid:
        playlist = Playlist.query.get(uuid)
    if not playlist:
        playlist = Playlist(uuid=uuid, userid=session['userid'])
    playlist.modified_at = datetime.now()
    playlist.contents = hashes
    Session.add(playlist)
    Session.commit()
    return jsonify(uuid=playlist.uuid, status=True)


@app.route('/json/playlist/get/<uuid>')
def playlist_info_json(uuid):
    playlist = Playlist.query.get(uuid)
    if not playlist:
        abort(404)
    return jsonify(playlist.to_dict())


@app.route('/json/search')
def search_json():
    query = request.args.get('q')
    num_results = int(request.args.get('n', 100))
    results = []
    if query:
        resultiter = searcher.search(query, n=num_results)
        results = [{'score': score, 'sha1': sha1}
                   for (score, sha1) in resultiter]
    return jsonify(results=results)


@app.route('/autocomplete')
def autocomplete_search():
    term = request.args.get('term')
    if not term:
        abort(400)
    possible_artists = Session.query(distinct(MP3.artist)).filter(MP3.artist.like('%s%%' % term))
    return jsonify(results=[x[0] for x in possible_artists])


@app.route('/fragment/songs', methods=['POST'])
def songs_fragment():
    hashes = request.form.get('h', '')
    if hashes:
        mp3s = [MP3.query.get(h) for h in hashes.split(',')]
    else:
        mp3s = []
    return render_template('songs_fragment.html', songs=mp3s)


@app.route('/')
def homepage():
    return render_template('index.html', userid=session['userid'])


def fileiter(path, pos, end):
    with open(path, 'r') as fd:
        fd.seek(pos)
        while True:
            n = min(65536, end - pos)
            chunk = fd.read(n)
            if not chunk:
                break
            yield chunk
            pos += len(chunk)


@app.route('/dl/<sha1>')
def download_song(sha1):
    mp3 = MP3.query.get(sha1)
    if not mp3:
        abort(404)

    # Parse 'Range' HTTP header.
    start = 0
    end = os.path.getsize(mp3.path)
    partial = False
    if 'Range' in request.headers:
        partial = True
        hrange = request.headers['Range']
        if hrange.startswith('bytes='):
            hrange = hrange[6:]
        if '-' in hrange:
            sstart, send = hrange.split('-')
            if sstart:
                start = int(sstart)
            if send:
                end = int(send)
        else:
            end = int(hrange)

    response = Response(fileiter(mp3.path, start, end),
                        status=(partial and 206 or 200),
                        direct_passthrough=True)
    response.headers['Accept-Ranges'] = 'bytes'
    if partial:
        response.headers['Content-Range'] = 'bytes %d-%d/%d' % (
            start, end - 1, os.path.getsize(mp3.path))
    response.content_type = 'audio/mpeg'
    response.content_length = (end - start)
    return response


def run_frontend(port, storage_dir, solr_url, db_url, lastfm_api_key,
                 album_art_dir):
    global storage_root, searcher, album_images
    storage_root = storage_dir
    searcher = search.Searcher(solr_url)
    album_images = AlbumImageRetriever(lastfm_api_key, album_art_dir)
    init_db(db_url)

    from gevent.wsgi import WSGIServer
    http_server = WSGIServer(('0.0.0.0', port), app)
    http_server.serve_forever()


def main():
    parser = optparse.OptionParser()
    parser.add_option('--solr_url', default='http://localhost:8080/solr')
    parser.add_option('--port', type='int', default=3003)
    parser.add_option('--storage_dir')
    parser.add_option('--db_url')
    parser.add_option('--lastfm_api_key')
    parser.add_option('--album_art_dir', default='/var/tmp/album-image-cache')
    daemonize.add_standard_options(parser)
    utils.read_config_defaults(
        parser, os.getenv('DJRANDOM_CONF', '/etc/djrandom.conf'))
    opts, args = parser.parse_args()
    if not opts.storage_dir:
        parser.error('Must provide --storage_dir')
    if not opts.db_url:
        parser.error('Must provide --db_url')
    if args:
        parser.error('Too many arguments')

    daemonize.daemonize(opts, run_frontend,
                        (opts.port, opts.storage_dir, opts.solr_url,
                         opts.db_url, opts.lastfm_api_key, opts.album_art_dir),
                        support_gevent=True)


if __name__ == '__main__':
    main()