var ZipEntry = require("./zipEntry"),
    Headers = require("./headers"),
    Utils = require("./util");

module.exports = function(/*String|Buffer*/input, /*Number*/inputType) {
    var entryList = [],
        entryTable = {},
        _comment = new Buffer(0),
        filename = "",
        fs = require("fs"),
        inBuffer = null,
        mainHeader = new Headers.MainHeader();

    if (inputType == Utils.Constants.FILE) {
        // is a filename
        filename = input;
        inBuffer = fs.readFileSync(filename);
        readMainHeader();
    } else if (inputType == Utils.Constants.BUFFER) {
        // is a memory buffer
        inBuffer = input;
        readMainHeader();
    } else {
        // none. is a new file
    }

    function readEntries() {
        entryTable = {};
        entryList = new Array(mainHeader.diskEntries);  // total number of entries
        var index = mainHeader.offset;  // offset of first CEN header
        for(var i = 0; i < entryList.length; i++) {

            var tmp = index,
                entry = new ZipEntry(inBuffer);
            entry.header = inBuffer.slice(tmp, tmp += Utils.Constants.CENHDR);

            entry.entryName = inBuffer.slice(tmp, tmp += entry.header.fileNameLength);

            if (entry.header.extraLength) {
                entry.extra = inBuffer.slice(tmp, tmp += entry.header.extraLength);
            }

            if (entry.header.commentLength)
                entry.comment = inBuffer.slice(tmp, tmp + entry.header.commentLength);

            index += entry.header.entryHeaderSize;

            entryList[i] = entry;
            entryTable[entry.entryName] = entry;
        }
    }

    function readMainHeader() {
        var i = inBuffer.length - Utils.Constants.ENDHDR, // END header size
            n = Math.max(0, i - 0xFFFF), // 0xFFFF is the max zip file comment length
            endOffset = -1; // Start offset of the END header

        for (i; i >= n; i--) {
            if (inBuffer[i] != 0x50) continue; // quick check that the byte is 'P'
            if (inBuffer.readUInt32LE(i) == Utils.Constants.ENDSIG) { // "PK\005\006"
                endOffset = i;
                break;
            }
        }
        if (!~endOffset)
            throw Utils.Errors.INVALID_FORMAT;

        mainHeader.loadFromBinary(inBuffer.slice(endOffset, endOffset + Utils.Constants.ENDHDR));
        if (mainHeader.commentLength) {
            _comment = inBuffer.slice(endOffset + Utils.Constants.ENDHDR);
        }
        readEntries();
    }

    return {
        /**
         * Returns an array of ZipEntry objects existent in the current opened archive
         * @return Array
         */
        get entries () {
            return entryList;
        },

        /**
         * Archive comment
         * @return {String}
         */
        get comment () { return _comment.toString(); },
        set comment(val) {
            mainHeader.commentLength = val.length;
            _comment = val;
        },

        /**
         * Returns a reference to the entry with the given name or null if entry is inexistent
         *
         * @param entryName
         * @return ZipEntry
         */
        getEntry : function(/*String*/entryName) {
            return entryTable[entryName] || null;
        },

        /**
         * Adds the given entry to the entry list
         *
         * @param entry
         */
        setEntry : function(/*ZipEntry*/entry) {
            entryList.push(entry);
            entryTable[entry.entryName] = entry;
            mainHeader.totalEntries = entryList.length;
        },

        /**
         * Removes the entry with the given name from the entry list.
         *
         * If the entry is a directory, then all nested files and directories will be removed
         * @param entryName
         */
        deleteEntry : function(/*String*/entryName) {
            var entry = entryTable[entryName];
            if (entry && entry.isDirectory) {
                var _self = this;
                this.getEntryChildren(entry).forEach(function(child) {
                    if (child.entryName != entryName) {
                        _self.deleteEntry(child.entryName)
                    }
                })
            }
            entryList.splice(entryList.indexOf(entry), 1);
            delete(entryTable[entryName]);
            mainHeader.totalEntries = entryList.length;
        },

        /**
         *  Iterates and returns all nested files and directories of the given entry
         *
         * @param entry
         * @return Array
         */
        getEntryChildren : function(/*ZipEntry*/entry) {
            if (entry.isDirectory) {
                var list = [],
                    name = entry.entryName,
                    len = name.length;

                entryList.forEach(function(zipEntry) {
                    if (zipEntry.entryName.substr(0, len) == name) {
                        list.push(zipEntry);
                    }
                });
                return list;
            }
            return []
        },

        /**
         * Returns the zip file
         *
         * @return Buffer
         */
        compressToBuffer : function() {
            if (entryList.length > 1) {
                entryList.sort(function(a, b) {
                    var nameA = a.entryName.toLowerCase();
                    var nameB = b.entryName.toLowerCase();
                    if (nameA < nameB) {return -1}
                    if (nameA > nameB) {return 1}
                    return 0;
                });
            }

            var totalSize = 0,
                dataBlock = [],
                entryHeaders = [],
                dindex = 0;

            mainHeader.size = 0;
            mainHeader.offset = 0;

            entryList.forEach(function(entry) {
                entry.header.offset = dindex;

                // compress data and set local and entry header accordingly. Reason why is called first
                var compressedData = entry.getCompressedData();
                // data header
                var dataHeader = entry.header.dataHeaderToBinary();
                var postHeader = new Buffer(entry.entryName + entry.extra.toString());
                var dataLength = dataHeader.length + postHeader.length + compressedData.length;

                dindex += dataLength;

                dataBlock.push(dataHeader);
                dataBlock.push(postHeader);
                dataBlock.push(compressedData);

                var entryHeader = entry.packHeader();
                entryHeaders.push(entryHeader);
                mainHeader.size += entryHeader.length;
                totalSize += (dataLength + entryHeader.length);
            });

            totalSize += mainHeader.mainHeaderSize; // also includes zip file comment length
            // point to end of data and begining of central directory first record
            mainHeader.offset = dindex;

            dindex = 0;
            var outBuffer = new Buffer(totalSize);
            dataBlock.forEach(function(content) {
                content.copy(outBuffer, dindex); // write data blocks
                dindex += content.length;
            });
            entryHeaders.forEach(function(content) {
                content.copy(outBuffer, dindex); // write central directory entries
                dindex += content.length;
            });

            var mh = mainHeader.toBinary();
            if (_comment) {
                _comment.copy(mh, Utils.Constants.ENDHDR); // add zip file comment
            }

            mh.copy(outBuffer, dindex); // write main header

            return outBuffer
        },

        toAsyncBuffer : function(/*Function*/onSuccess,/*Function*/onFail,/*Function*/onItemStart,/*Function*/onItemEnd) {
            if (entryList.length > 1) {
                entryList.sort(function(a, b) {
                    var nameA = a.entryName.toLowerCase();
                    var nameB = b.entryName.toLowerCase();
                    if (nameA > nameB) {return -1}
                    if (nameA < nameB) {return 1}
                    return 0;
                });
            }

            var totalSize = 0,
                dataBlock = [],
                entryHeaders = [],
                dindex = 0;

            mainHeader.size = 0;
            mainHeader.offset = 0;

            var compress=function(entryList){
                var self=arguments.callee;
                var entry;
                if(entryList.length){
                    var entry=entryList.pop();
                    var name=entry.entryName + entry.extra.toString();
                    if(onItemStart)onItemStart(name);
                    entry.getCompressedDataAsync(function(compressedData){
                        if(onItemEnd)onItemEnd(name);

                        entry.header.offset = dindex;
                        // data header
                        var dataHeader = entry.header.dataHeaderToBinary();
                        var postHeader = new Buffer(name);
                        var dataLength = dataHeader.length + postHeader.length + compressedData.length;

                        dindex += dataLength;

                        dataBlock.push(dataHeader);
                        dataBlock.push(postHeader);
                        dataBlock.push(compressedData);

                        var entryHeader = entry.packHeader();
                        entryHeaders.push(entryHeader);
                        mainHeader.size += entryHeader.length;
                        totalSize += (dataLength + entryHeader.length);

                        if(entryList.length){
                            self(entryList);
                        }else{


                            totalSize += mainHeader.mainHeaderSize; // also includes zip file comment length
                            // point to end of data and begining of central directory first record
                            mainHeader.offset = dindex;

                            dindex = 0;
                            var outBuffer = new Buffer(totalSize);
                            dataBlock.forEach(function(content) {
                                content.copy(outBuffer, dindex); // write data blocks
                                dindex += content.length;
                            });
                            entryHeaders.forEach(function(content) {
                                content.copy(outBuffer, dindex); // write central directory entries
                                dindex += content.length;
                            });

                            var mh = mainHeader.toBinary();
                            if (_comment) {
                                _comment.copy(mh, Utils.Constants.ENDHDR); // add zip file comment
                            }

                            mh.copy(outBuffer, dindex); // write main header

                            onSuccess(outBuffer);
                        }
                    });
                }
            };

            compress(entryList);
        }
    }
};
