"""
Modplug metadata inserted into module files.

Doc:
- http://modplug.svn.sourceforge.net/viewvc/modplug/trunk/modplug/soundlib/

Author: Christophe GISQUET <christophe.gisquet@free.fr>
Creation: 10th February 2007
"""

from hachoir.field import (FieldSet,
                           UInt32, UInt16, UInt8, Int8, Float32,
                           RawBytes, String, GenericVector, ParserError)
from hachoir.core.endian import LITTLE_ENDIAN
from hachoir.core.text_handler import textHandler, hexadecimal

MAX_ENVPOINTS = 32


def parseComments(parser):
    size = parser["block_size"].value
    if size > 0:
        yield String(parser, "comment", size)


class MidiOut(FieldSet):
    static_size = 9 * 32 * 8

    def createFields(self):
        for name in ("start", "stop", "tick", "noteon", "noteoff",
                     "volume", "pan", "banksel", "program"):
            yield String(self, name, 32, strip='\0')


class Command(FieldSet):
    static_size = 32 * 8

    def createFields(self):
        start = self.absolute_address
        size = self.stream.searchBytesLength(b"\0", False, start)
        if size > 0:
            self.info("Command: %s" % self.stream.readBytes(start, size))
            yield String(self, "command", size, strip='\0')
        yield RawBytes(self, "parameter", (self._size // 8) - size)


class MidiSFXExt(FieldSet):
    static_size = 16 * 32 * 8

    def createFields(self):
        for index in range(16):
            yield Command(self, "command[]")


class MidiZXXExt(FieldSet):
    static_size = 128 * 32 * 8

    def createFields(self):
        for index in range(128):
            yield Command(self, "command[]")


def parseMidiConfig(parser):
    yield MidiOut(parser, "midi_out")
    yield MidiSFXExt(parser, "sfx_ext")
    yield MidiZXXExt(parser, "zxx_ext")


def parseChannelSettings(parser):
    size = parser["block_size"].value // 4
    if size > 0:
        yield GenericVector(parser, "settings", size, UInt32, "mix_plugin")


def parseEQBands(parser):
    size = parser["block_size"].value // 4
    if size > 0:
        yield GenericVector(parser, "gains", size, UInt32, "band")


class SoundMixPluginInfo(FieldSet):
    static_size = 128 * 8

    def createFields(self):
        yield textHandler(UInt32(self, "plugin_id1"), hexadecimal)
        yield textHandler(UInt32(self, "plugin_id2"), hexadecimal)
        yield UInt32(self, "input_routing")
        yield UInt32(self, "output_routing")
        yield GenericVector(self, "routing_info", 4, UInt32, "reserved")
        yield String(self, "name", 32, strip='\0')
        yield String(self, "dll_name", 64, desc="Original DLL name", strip='\0')


class ExtraData(FieldSet):

    def __init__(self, parent, name, desc=None):
        FieldSet.__init__(self, parent, name, desc)
        self._size = (4 + self["size"].value) * 8

    def createFields(self):
        yield UInt32(self, "size")
        size = self["size"].value
        if size:
            yield RawBytes(self, "data", size)


class XPlugData(FieldSet):

    def __init__(self, parent, name, desc=None):
        FieldSet.__init__(self, parent, name, desc)
        self._size = (4 + self["size"].value) * 8

    def createFields(self):
        yield UInt32(self, "size")
        while not self.eof:
            yield UInt32(self, "marker")
            if self["marker"].value == 'DWRT':
                yield Float32(self, "dry_ratio")
            elif self["marker"].value == 'PORG':
                yield UInt32(self, "default_program")


def parsePlugin(parser):
    yield SoundMixPluginInfo(parser, "info")

    # Check if VST setchunk present
    size = parser.stream.readBits(
        parser.absolute_address + parser.current_size, 32, LITTLE_ENDIAN)
    if 0 < size < parser.current_size + parser._size:
        yield ExtraData(parser, "extra_data")

    # Check if XPlugData is present
    size = parser.stream.readBits(
        parser.absolute_address + parser.current_size, 32, LITTLE_ENDIAN)
    if 0 < size < parser.current_size + parser._size:
        yield XPlugData(parser, "xplug_data")


# Format: "XXXX": (type, count, name)
EXTENSIONS = {
    # WriteInstrumentHeaderStruct@Sndfile.cpp
    "XTPM": {
        "..Fd": (UInt32, 1, "Flags"),
        "..OF": (UInt32, 1, "Fade out"),
        "..VG": (UInt32, 1, "Global Volume"),
        "...P": (UInt32, 1, "Panning"),
        "..EV": (UInt32, 1, "Volume Envelope"),
        "..EP": (UInt32, 1, "Panning Envelope"),
        ".EiP": (UInt32, 1, "Pitch Envelope"),
        ".SLV": (UInt8, 1, "Volume Loop Start"),
        ".ELV": (UInt8, 1, "Volume Loop End"),
        ".BSV": (UInt8, 1, "Volume Sustain Begin"),
        ".ESV": (UInt8, 1, "Volume Sustain End"),
        ".SLP": (UInt8, 1, "Panning Loop Start"),
        ".ELP": (UInt8, 1, "Panning Loop End"),
        ".BSP": (UInt8, 1, "Panning Substain Begin"),
        ".ESP": (UInt8, 1, "Padding Substain End"),
        "SLiP": (UInt8, 1, "Pitch Loop Start"),
        "ELiP": (UInt8, 1, "Pitch Loop End"),
        "BSiP": (UInt8, 1, "Pitch Substain Begin"),
        "ESiP": (UInt8, 1, "Pitch Substain End"),
        ".ANN": (UInt8, 1, "NNA"),
        ".TCD": (UInt8, 1, "DCT"),
        ".AND": (UInt8, 1, "DNA"),
        "..SP": (UInt8, 1, "Panning Swing"),
        "..SV": (UInt8, 1, "Volume Swing"),
        ".CFI": (UInt8, 1, "IFC"),
        ".RFI": (UInt8, 1, "IFR"),
        "..BM": (UInt32, 1, "Midi Bank"),
        "..PM": (UInt8, 1, "Midi Program"),
        "..CM": (UInt8, 1, "Midi Channel"),
        ".KDM": (UInt8, 1, "Midi Drum Key"),
        ".SPP": (Int8, 1, "PPS"),
        ".CPP": (UInt8, 1, "PPC"),
        ".[PV": (UInt32, MAX_ENVPOINTS, "Volume Points"),
        ".[PP": (UInt32, MAX_ENVPOINTS, "Panning Points"),
        "[PiP": (UInt32, MAX_ENVPOINTS, "Pitch Points"),
        ".[EV": (UInt8, MAX_ENVPOINTS, "Volume Enveloppe"),
        ".[EP": (UInt8, MAX_ENVPOINTS, "Panning Enveloppe"),
        "[EiP": (UInt8, MAX_ENVPOINTS, "Pitch Enveloppe"),
        ".[MN": (UInt8, 128, "Note Mapping"),
        "..[K": (UInt32, 128, "Keyboard"),
        "..[n": (String, 32, "Name"),
        ".[nf": (String, 12, "Filename"),
        ".PiM": (UInt8, 1, "MixPlug"),
        "..RV": (UInt16, 1, "Volume Ramping"),
        "...R": (UInt16, 1, "Resampling"),
        "..SC": (UInt8, 1, "Cut Swing"),
        "..SR": (UInt8, 1, "Res Swing"),
        "..MF": (UInt8, 1, "Filter Mode"),
    },

    # See after "CODE tag dictionary", same place, elements with [EXT]
    "STPM": {
        "...C": (UInt32, 1, "Channels"),
        ".VWC": (None, 0, "CreatedWith version"),
        ".VGD": (None, 0, "Default global volume"),
        "..TD": (None, 0, "Default tempo"),
        "HIBE": (None, 0, "Embedded instrument header"),
        "VWSL": (None, 0, "LastSavedWith version"),
        ".MMP": (None, 0, "Plugin Mix mode"),
        ".BPR": (None, 0, "Rows per beat"),
        ".MPR": (None, 0, "Rows per measure"),
        "@PES": (None, 0, "Chunk separator"),
        ".APS": (None, 0, "Song Pre-amplification"),
        "..MT": (None, 0, "Tempo mode"),
        "VTSV": (None, 0, "VSTi volume"),
    }
}


class MPField(FieldSet):

    def __init__(self, parent, name, ext, desc=None):
        FieldSet.__init__(self, parent, name, desc)
        self.ext = ext
        self.info(self.createDescription())
        self._size = (6 + self["data_size"].value) * 8

    def createFields(self):
        # Identify tag
        code = self.stream.readBytes(self.absolute_address, 4)
        if code in self.ext:
            cls, count, comment = self.ext[code]
        else:
            cls, count, comment = RawBytes, 1, "Unknown tag"

        # Header
        yield String(self, "code", 4, comment)
        yield UInt16(self, "data_size")

        # Data
        if not cls:
            size = self["data_size"].value
            if size > 0:
                yield RawBytes(self, "data", size)
        elif cls in (String, RawBytes):
            yield cls(self, "value", count)
        else:
            if count > 1:
                yield GenericVector(self, "values", count, cls, "item")
            else:
                yield cls(self, "value")

    def createDescription(self):
        return "Element '%s', size %i" % \
               (self["code"]._description, self["data_size"].value)


def parseFields(parser):
    # Determine field names
    ext = EXTENSIONS[parser["block_type"].value]
    if ext is None:
        raise ParserError("Unknown parent '%s'" % parser["block_type"].value)

    # Parse fields
    addr = parser.absolute_address + parser.current_size
    while not parser.eof and parser.stream.readBytes(addr, 4) in ext:
        field = MPField(parser, "field[]", ext)
        yield field
        addr += field._size

    # Abort on unknown codes
    parser.info("End of extension '%s' when finding '%s'" %
                (parser["block_type"].value, parser.stream.readBytes(addr, 4)))


class ModplugBlock(FieldSet):
    BLOCK_INFO = {
        "TEXT": ("comment", True, "Comment", parseComments),
        "MIDI": ("midi_config", True, "Midi configuration", parseMidiConfig),
        "XFHC": ("channel_settings", True, "Channel settings", parseChannelSettings),
        "XTPM": ("instrument_ext", False, "Instrument extensions", parseFields),
        "STPM": ("song_ext", False, "Song extensions", parseFields),
    }

    def __init__(self, parent, name, desc=None):
        FieldSet.__init__(self, parent, name, desc)
        self.parseBlock = parsePlugin

        t = self["block_type"].value
        self.has_size = False
        if t in self.BLOCK_INFO:
            self._name, self.has_size, desc, parseBlock = self.BLOCK_INFO[t]
            if callable(desc):
                self.createDescription = lambda: desc(self)
            if parseBlock:
                self.parseBlock = lambda: parseBlock(self)

        if self.has_size:
            self._size = 8 * (self["block_size"].value + 8)

    def createFields(self):
        yield String(self, "block_type", 4)
        if self.has_size:
            yield UInt32(self, "block_size")

        if self.parseBlock:
            yield from self.parseBlock()

        if self.has_size:
            size = self["block_size"].value - (self.current_size // 8)
            if size > 0:
                yield RawBytes(self, "data", size, "Unknown data")


def ParseModplugMetadata(parser):
    while not parser.eof:
        block = ModplugBlock(parser, "block[]")
        yield block
        if block["block_type"].value == "STPM":
            break

    # More undocumented stuff: date ?
    size = (parser._size - parser.absolute_address - parser.current_size) // 8
    if size > 0:
        yield RawBytes(parser, "info", size)
