# This file is part of beets.
# Copyright 2013, Adrian Sampson.
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.

"""The core data store and collection logic for beets.
"""
import os
import re
import sys
import logging
import shlex
import unicodedata
import traceback
import time
from unidecode import unidecode
from beets.mediafile import MediaFile
from beets import plugins
from beets import util
from beets.util import bytestring_path, syspath, normpath, samefile
from beets.util.functemplate import Template
from beets import dbcore
from beets.dbcore import types
import beets



# Library-specific query types.


class PathQuery(dbcore.FieldQuery):
    """A query that matches all items under a given path."""
    def __init__(self, field, pattern, fast=True):
        super(PathQuery, self).__init__(field, pattern, fast)

        # Match the path as a single file.
        self.file_path = util.bytestring_path(util.normpath(pattern))
        # As a directory (prefix).
        self.dir_path = util.bytestring_path(os.path.join(self.file_path, ''))

    def match(self, item):
        return (item.path == self.file_path) or \
               item.path.startswith(self.dir_path)

    def clause(self):
        dir_pat = buffer(self.dir_path + '%')
        file_blob = buffer(self.file_path)
        return '({0} = ?) || ({0} LIKE ?)'.format(self.field), \
               (file_blob, dir_pat)


class SingletonQuery(dbcore.Query):
    """Matches either singleton or non-singleton items."""
    def __init__(self, sense):
        self.sense = sense

    def clause(self):
        if self.sense:
            return "album_id ISNULL", ()
        else:
            return "NOT album_id ISNULL", ()

    def match(self, item):
        return (not item.album_id) == self.sense



# Library-specific field types.


class DateType(types.Type):
    sql = u'REAL'
    query = dbcore.query.DateQuery

    def format(self, value):
        return time.strftime(beets.config['time_format'].get(unicode),
                             time.localtime(value or 0))

    def parse(self, string):
        try:
            # Try a formatted date string.
            return time.mktime(
                time.strptime(string, beets.config['time_format'].get(unicode))
            )
        except ValueError:
            # Fall back to a plain timestamp number.
            try:
                return float(string)
            except ValueError:
                return 0.0


class PathType(types.Type):
    sql = u'BLOB'
    query = PathQuery

    def format(self, value):
        return util.displayable_path(value)

    def parse(self, string):
        return normpath(bytestring_path(string))



# Model field lists.


# Fields in the "items" database table; all the metadata available for
# items in the library. These are used directly in SQL; they are
# vulnerable to injection if accessible to the user.
# Each tuple has the following values:
# - The name of the field.
# - The (Python) type of the field.
# - Is the field writable?
# - Does the field reflect an attribute of a MediaFile?
ITEM_FIELDS = [
    ('id',       types.Id(),      False, False),
    ('path',     PathType(),      False, False),
    ('album_id', types.Integer(), False, False),

    ('title',                types.String(),     True, True),
    ('artist',               types.String(),     True, True),
    ('artist_sort',          types.String(),     True, True),
    ('artist_credit',        types.String(),     True, True),
    ('album',                types.String(),     True, True),
    ('albumartist',          types.String(),     True, True),
    ('albumartist_sort',     types.String(),     True, True),
    ('albumartist_credit',   types.String(),     True, True),
    ('genre',                types.String(),     True, True),
    ('composer',             types.String(),     True, True),
    ('grouping',             types.String(),     True, True),
    ('year',                 types.PaddedInt(4), True, True),
    ('month',                types.PaddedInt(2), True, True),
    ('day',                  types.PaddedInt(2), True, True),
    ('track',                types.PaddedInt(2), True, True),
    ('tracktotal',           types.PaddedInt(2), True, True),
    ('disc',                 types.PaddedInt(2), True, True),
    ('disctotal',            types.PaddedInt(2), True, True),
    ('lyrics',               types.String(),     True, True),
    ('comments',             types.String(),     True, True),
    ('bpm',                  types.Integer(),    True, True),
    ('comp',                 types.Boolean(),    True, True),