#!/usr/bin/env python
# Copyright 2006, Paul Sladen.  Do with as you wish, hope it's useful.
# See:  http://www.paul.sladen.org/toys/samsung-yh-925/

# some pointers from: http://www.misticriver.net/wiki/index.php/H10_Database_Specification
# and a programs called 'philips-hdd100.py'.

import struct, sys
from encodings import utf_16_le
import string

class sdb:
    def build_format_string(self):
        s = ''
        for name, type, size in self.fields:
            s += self.field_pack_format[type]
        self.format_string = s
        return s
        
    field_pack_format = {int: 'I',
                         unicode: 's'}

    def __init__(self):
        self.data = {}

    def unpack(self, s):
        u = list(struct.unpack(self.format_string, s))
        for name, type in self.fields:
            self.data[name] = type(u.pop(0))

    def unpack2(self, fp, record_size = 0x220):
        #print self.fields
        for name, type, size in self.fields:
            #print record_size
            if type == unicode:
                size = 512
                #print 'Unicode, eating 512'
                u = utf_16_le.decode(f.read(size))
                record_size -= size
                self.data[name] = type(u[0]).strip('\x00')
            else:
                ts = self.field_pack_format[type]
                size = struct.calcsize(ts)
                (u,) = struct.unpack(ts, fp.read(size))
                self.data[name] = type(u)
                record_size -= size
        
    def unpack3(self, fp, variable=False):
        record_size = 0
        for name, type, size in self.fields:
            if type == unicode and not variable:
                size *= 2
                u = utf_16_le.decode(fp.read(size))
                self.data[name] = type(u[0]).strip('\0')
                #print 'l:', len(self.data[name])
                record_size += size

                # Gah, variable length strings!
            elif type == unicode and variable:
                u = ''
                for i in range(size+1):
                    s = fp.read(2)
                    if s == '\x00\x00':
                        #print 'matched:', `s`, `'\x00\x00'`, 'so far:', `u`, size
                        break
                    u += s
                self.data[name] = unicode(utf_16_le.decode(u)[0])
                #print self.data[name], i, i % 2, fp.tell()
                #print self.data[name]
                
            else:
                ts = self.field_pack_format[type]
                (u,) = struct.unpack(ts, fp.read(size))
                self.data[name] = type(u)
                record_size += size
        #print record_size
        return self.data

    def headings(self):
        h = []
        for name, type, size in self.fields:
            h.append(name)
        return h

class PPOS_hdr_entry(sdb):
    fields = [('id', int, 4),
              ('type', int, 4),
              ('length', int, 4),
              ('foo5', int, 4),
              ('foo6', int, 4),
              ('indexed', int, 4),
              ('foo7', int, 4),
              ('foo8', int, 4),
              ('idx_filename', unicode, 256)]
    allowed = {'type': {0: None,
                        1: unicode,
                        2: int}}

class PPOS_hdr_header(sdb):
    fields = [('id',   int, 4),
              ('type', int, 4),
              ('datafile', unicode, 256),
              ('foo4', int, 4),
              ('headerfile', unicode, 256),
              ('foo6', int, 4),
              ('rows', int, 4),
              ('inactive', int, 4),
              ('columns', int, 4) ]

class Table:
    def __init__(self, headings):
        self.rows = []
        self.headings = headings
    def add_from_dict(self, d):
        row = [None] * len(self.headings)
        #print d
        for key, value in d.items():
            row[self.headings.index(key)] = value
        self.rows.append(row)
    def __str__(self):
        r = [self.headings] + self.rows
        # leet!
        widths = map(max, zip(*[[len(str(i)) for i in c] for c in r]))
        #print 'widths:', widths

        def str_with_width(items, widths):
            c = {int: str.rjust,
                 str: str.ljust,
                 unicode: str.ljust}
            l = []
            #for i, w in zip(items, widths):
            #    l.append(c[type(i)](str(i), w))
            #return l
            #return [c[type(i)](str(i), w) for i, w in zip(items, widths)]
            return [str.ljust(str(i), w) for i, w in zip(items, widths)]
                
        # gross!
        s = '| ' + string.join(
            [string.join(str_with_width(row, widths), ' | ') for row in r],
            ' |\n| ') + ' |\n'
        return s
    def column(self, heading):
        c = self.headings.index(heading)
        l = []
        for row in self.rows:
            l.append(row[c])
        return l
    def row(self, number):
        return self.rows[number]
    def where(self, heading, match):
        t = Table(self.headings)
        c = self.headings.index(heading)
        for row in self.rows:
            if row[c] == match:
                t.rows.append(row)
        return t
    def select(self, *headings):
        t = Table(self.headings)
        cols = map(self.headings.index, headings)
        for row in self.rows:
            r = []
            for col in cols:
                r.append(row[col])
            t.rows.append(r)
        selected = []
        for h in cols:
            selected.append(self.headings[h])
        t.headings = selected
        return t

if __name__ == '__main__':
    #d.build_format_string()
    if len(sys.argv) > 1:
        file = sys.argv[1]
    else:
        file = '../backup/System/DATA/PP5000.hdr' 

    f = open(file, 'rb')

    #t = Table(d.headings())
    #f.seek(0x19c)
    #t.add_from_dict(d.unpack3(f))
    # brings us to 0x3bc and then skip bytes up to 0x41c
    #f.seek(96, 1)
    #for(i) in range(19):
        #t.add_from_dict(d.unpack3(f))
    #print t

    d = PPOS_hdr_entry()
    e = PPOS_hdr_header()

    dt = Table(d.headings())
    de = Table(e.headings())

    de.add_from_dict(e.unpack3(f))
    print de
    num_records = de.select('columns').rows[0][0]
    num_songs = de.select('rows').rows[0][0]

    #print f.tell()
    for(i) in range(num_records):
        dt.add_from_dict(d.unpack3(f))
    print
    print dt

    #print dt.select('indexed', 'idx_filename').where('indexed', 1)
    #print dt.where('type', 2)

    r = dt.select('type', 'length', 'idx_filename').rows
    types = {0: 'null', 1: 'unicode', 2: 'int'}
    i = 0
    fields = []
    for type, length, file in r:
        name = str(file[-8:-4])
        if not name:
            name = 'x' + str(i)
        i += 1
        #print '(\'' + name + '\', ' + types[type] + ', ' + str(length) + '),'
        fields.append((name, PPOS_hdr_entry.allowed['type'][type], length))

    PPOS_dat_entry = sdb
    PPOS_dat_entry.fields = fields
    ed = PPOS_dat_entry()

    gp = open('../backup/System/DATA/PP5000.dat', 'rb')
    gp.seek(8)
    edt = Table(ed.headings())

    for i in range(num_songs):
        edt.add_from_dict(ed.unpack3(gp, variable=True))
        #print `gp.read(8)`
        gp.seek(8, 1)
        #print `ed.unpack3(gp, variable=True)`
    print edt

    #print f.tell()
