#include "downloadablefile.h"

#include "binaryverifier.h"
#include "bitstreamverifier.h"

#include <QFile>
#include <QFileInfo>
#include <QtEndian>

#include "miniz.h"

#include <string.h>

#include "mydebug.h"

class ZipHelper
{
public:
    enum
    {
      MZ_ZIP_FLAG_CASE_SENSITIVE                = 0x0100,
      MZ_ZIP_FLAG_IGNORE_PATH                   = 0x0200,
      MZ_ZIP_FLAG_COMPRESSED_DATA               = 0x0400,
      MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY = 0x0800
    } mz_zip_flags;

    struct bsk_zip_info_t
    {
      int valid;
      int compressed;
      unsigned int compressed_size;
      unsigned int uncompressed_size;
      const char* filename;
      unsigned int crc32;
      unsigned int data_offset;
      const void* data_start;
    };

    struct zip_file_header_t
    {
      unsigned char header_signature[4];
      unsigned char ver_extractor[2];
      unsigned char flags[2];
      unsigned char comp_method[2];
      unsigned char mod_time[2];
      unsigned char mod_date[2];
      unsigned char crc32[4];
      unsigned char csize[4];
      unsigned char usize[4];
      unsigned char fname_len[2];
      unsigned char extra_len[2];
    };

    bsk_zip_info_t mzi;

    char buffer[1024];

    bool check(QFile& f)
    {
        QByteArray d=f.read(4096);
        return check(d);
    }

    bool check(const QByteArray& d)
    {
        //QByteArray d=f.read(4096);

        const char* image=(const char*)d.constData();

        const struct zip_file_header_t* zh=(const struct zip_file_header_t*)image;
        if (!(zh->header_signature[0]=='P' && zh->header_signature[1]=='K' && zh->header_signature[2]==3 && zh->header_signature[3]==4))
          return false; //not a zip file

        unsigned long csize=mz_b2l(zh->csize);
        unsigned long usize=mz_b2l(zh->usize);

        unsigned short fname_len=mz_b2s(zh->fname_len);
        unsigned short extra_len=mz_b2s(zh->extra_len);

        unsigned short flags=mz_b2s(zh->flags);

        bsk_zip_info_t* zi=&mzi;
        memset(zi, 0, sizeof *zi);
        zi->valid=1;
        zi->filename=buffer;

        zi->compressed=(flags & MZ_ZIP_FLAG_COMPRESSED_DATA) ? 1 : 0;
        zi->compressed|=csize!=usize ? 2 : 0;

        zi->compressed_size=csize;
        zi->uncompressed_size=usize;
        zi->crc32=mz_b2l(zh->crc32);

        if (!fname_len)
        {
          buffer[0]=0;
          zi->filename=0;
        }
        else
        {
          unsigned int len=fname_len;
          if (len>(sizeof(buffer)-1))
            len=sizeof(buffer)-1;
          unsigned int i;
          char* s=(char*)&image[sizeof(struct zip_file_header_t)];
          for(i=0; i<len; ++i)
            buffer[i]=s[i];
          buffer[i]=0;
          zi->filename=buffer;
        }

        zi->data_offset=sizeof(struct zip_file_header_t)+fname_len+extra_len;
        zi->data_start=&image[zi->data_offset];

        return true;
    }

    QByteArray uncompress(const void* src, unsigned int len, unsigned int expected_len)
    {
        unsigned char* p=new unsigned char[expected_len+1000];
        unsigned long dst_len=expected_len;

        //int mz_uncompress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len)
        int r=mz_uncompress(p, &dst_len, (const unsigned char*)src, len);
        if (r)
            return QByteArray();
        return QByteArray((char*)p, dst_len);
    }

    unsigned long mz_b2l(const unsigned char* b)
    {
      unsigned long l;
      memcpy(&l, b, sizeof l);
      return l;
    }

    unsigned short mz_b2s(const unsigned char* b)
    {
      unsigned short l;
      memcpy(&l, b, sizeof l);
      return l;
    }

    void mz_s2b(unsigned char* b, unsigned short s)
    {
      memcpy(b, &s, sizeof s);
    }

    void mz_l2b(unsigned char* b, unsigned long s)
    {
      memcpy(b, &s, sizeof s);
    }
};

DownloadableFile::DownloadableFile()
{
}

#include <QCryptographicHash>

bool DownloadableFile::open(const QString &filename)
{
    m_fn=filename;

    QFileInfo finfo(filename);

    m_info.filename=finfo.fileName();
    m_info.sourcefile=finfo.fileName();

    m_info.errStr="not yet validated";

    QFile f(m_fn);

    m_info.compressed_size=finfo.size();
    m_info.uncompressed_size=finfo.size();

    bool ok=f.open(QIODevice::ReadOnly);
    if (!ok)
    {
        m_info.errStr=f.errorString();
        return false;
    }

    QByteArray all=f.readAll();

    //Zipped?
    {
        ZipHelper zhelp;
        bool zipped=zhelp.check(all);
        if (zipped)
        {
            m_info.uncompressed_size=zhelp.mzi.uncompressed_size;
            m_info.filename=zhelp.mzi.filename;

            QByteArray tmp=zhelp.uncompress(&all.constData()[zhelp.mzi.data_offset], zhelp.mzi.compressed_size, zhelp.mzi.uncompressed_size);
            all=tmp;
            m_info.zip_crc32=zhelp.mzi.crc32;

            if (all.size()<=0)
            {
                m_info.errStr="uncompression fail";
                MyDebug.logMsg( type_log_error,
                                  origin_msg_generic ,
                                  "QUncompress fail!");
                return false;
            }
#if 0
            //This is very memory demanding...
            unsigned char exp_len[4];
            qToBigEndian(m_info.uncompressed_size, exp_len);
            QByteArray tmp((const char*)exp_len, 4);
            tmp+=all.mid(zhelp.mzi.data_offset);
            all=qUncompress(tmp);
            if (all.size()!=(int)m_info.uncompressed_size)
            {
                m_info.errStr="uncompression fail";
                MyDebug()<<"QUncompress fail!";
                return false;
            }
#endif
        }
    }

    f.close();

    QByteArray hash=QCryptographicHash::hash(all, QCryptographicHash::Md5);
    m_info.file_md5=QString(hash.toHex()).toUpper();

    BinaryVerifier v;
    ok=v.verify(all); //filename);
    m_info.binaryInfo=v.info();

    if (!ok)
    {
        return false;
    }

    if (m_info.binaryInfo.format()==BinaryInfo::Binary)
    {
        //Try bitstream
        BitstreamVerifier bv;
        //ok=bv.verify(filename);
        ok=bv.verify(all);
        if (ok)
        {
            m_info.binaryInfo.m_fmtName="Bitstream";
            m_info.binaryInfo.m_arch=BinaryInfo::Xilinx;
            m_info.binaryInfo.m_archName="Xilinx";
            m_info.binaryInfo.m_name=bv.info().name;
            m_info.binaryInfo.m_host=bv.info().user;
            m_info.binaryInfo.m_ftm=BinaryInfo::Bitstream;
            m_info.binaryInfo.m_generator=bv.info().generator;
            m_info.binaryInfo.m_pn=bv.info().partNumber;
            m_info.binaryInfo.m_date=bv.info().date;
            m_info.binaryInfo.m_partType=bv.info().repType;
            return true;
        }
        else
            return false;
    }
    else
        return true;
}
