/*
   Copyright 2010 Hanov Solutions Inc. All Rights Reserved

   steve.hanov@gmail.com
 */
/*jslint evil: true */

/** @constructor */ 
function Session()
{
    this.username = null;

    this.files = []; // array of { id, creationDate, accessDate, name }

}

Session.prototype = {

    makeAjaxRequest: function( strUrl, params, fnCallBack, param ) 
    {
        var xmlHttpReq;

        try {
            xmlHttpReq = new XMLHttpRequest();
        } catch ( trymicrosoft ) {
            try {
                xmlHttpReq = new ActiveXObject("Msxml2.XMLHTTP");
            } catch(othermicrosoft) {
                try {
                    xmlHttpReq = new ActiveXObject("Microsoft.XMLHTTP");
                } catch(failed) {
                    xmlHttpReq = null;
                }
            }
        }

        xmlHttpReq.open('POST', strUrl, true);
        xmlHttpReq.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
        xmlHttpReq.onreadystatechange = function() {
            if (xmlHttpReq.readyState === 4) {
                var result = { status: "", json: null };
                if (xmlHttpReq.status === 200 ) {
                    try {
                        //result.json = window.JSON.parse(xmlHttpReq.responseText);
                        result.json = eval("("+xmlHttpReq.responseText+")");
                        result.status = result.json["status"];
                    } catch( e ) {
                        alert(e);
                        result.status = "Error in server response";
                    }
                } else if ( xmlHttpReq.message ) {
                    result.status = xmlHttpReq.message;
                } else if ( xmlHttpReq.status === 0 ) {
                    result.status = "Network error. Check internet connection";
                } else {
                    result.status = 
                        "Server returned status " + xmlHttpReq.status;
                }
                fnCallBack( result );
            }
        };

        var query = "";
        var first = true;
        for ( var key in params ) {
            if ( params.hasOwnProperty( key ) ) {
                if ( !first ) {
                    query += '&';
                } 
                first = false;
                query += key + "=" + escape(params[key]);
            }
        }

        xmlHttpReq.send(query);
    },

    isLoggedIn: function()
    {
        return this.username !== null;
    },

    // function doneFunc( status, errorString )
    login: function( username, password, doneFunc )
    {
        var self = this;
        this.makeAjaxRequest("index.php",
            {
                "type": "login",
                "username": username,
                "password": password
            },

            function(result) {
                var mapping = null;
                if ( result.status === "ok" ) {
                    self.username = username;
                    self.files = result.json["files"];
                    self.files.sort( function( a, b ) {
                            return parseInt(b["modificationDate"],10) - parseInt(
                                a["modificationDate"],10); 
                        });
                    mapping = result.json["mapping"];
                    for ( var i = 0; i < self.files.length; i++ ) {
                        if ( self.files[i].id in mapping ) {
                            self.files[i].id = mapping[self.files[i].id];
                        }
                    }
                }
                doneFunc( result.status, mapping );
            }
        );
    },

    changePassword: function( oldPassword, newPassword, doneFunc )
    {
        this.makeAjaxRequest("index.php",
            {
                "type": "changepassword",
                "oldpassword": oldPassword,
                "newpassword": newPassword
            },

            function(result) {
                doneFunc( result.status );
            }
        );
    },

    createUser: function( username, password, doneFunc )
    {
        var self = this;
        this.makeAjaxRequest("index.php",
            {
                "type": "createuser",
                "username": username,
                "password": password
            },

            function(result) {
                if ( result.status === 'ok' ) {
                    self.username = username;
                    var mapping = result.json["mapping"];
                    for ( var i = 0; i < self.files.length; i++ ) {
                        if ( self.files[i].id in mapping ) {
                            self.files[i].id = mapping[self.files[i].id];
                        }
                    }
                }
                doneFunc( result.status, result.json["mapping"] );
            }
        );
    },

    // function doneFunc( status, errorString )
    logout: function(doneFunc)
    {
        var self = this;
        this.makeAjaxRequest("index.php",
            {
                "type": "logout"
            },

            function(result) {
                if ( result.status === 'ok' ) {
                    self.username = null;
                    self.files = [];
                }
                doneFunc( result.status );
            }
        );
    },

    // function doneFunc( status, errorString )
    getFileListing: function(doneFunc)
    {
        var self = this;
        this.makeAjaxRequest("index.php",
            {
                "type": "listfiles"
            },

            function(response) {
                if ( response.status === "ok" ) {
                    self.files = response.json["files"];
                    self.files.sort( function( a, b ) {
                            return parseInt(b.modificationDate,10) - parseInt(
                                a.modificationDate,10); 
                        });
                }
                doneFunc( response.status );
            }
        );
    },

    // function doneFunc( status, errorString, id )
    createFile: function( name, data, doneFunc )
    {
        var self = this;
        var d = Math.round((new Date()).getTime() / 1000 );
        this.makeAjaxRequest("index.php",
            {
                "type": "createfile",
                "name": name,
                "data": data
            },

            function(response) {
                if ( response.status === "ok" ) {
                    self.files.unshift( {
                        "id": response.json["id"],
                        "name": name,
                        "creationDate": d,
                        "modificationDate": d,
                        "shared": 0
                        });
                    doneFunc( response.status, response.json["id"] );
                } else {
                    doneFunc( response.status, -1 );
                }
            }
        );
    },

    // function doneFunc( status, errorString )
    updateFile: function( file, doneFunc )
    {
        var self = this;
        this.makeAjaxRequest("index.php",
            {
                "type": "updatefile",
                "id": file.id,
                "name": file.name,
                "data": file.data,
                "shared": file.shared
            },

            function(response) {
                if ( response.status === 'ok' ) {
                    for( var i = 0; i < self.files.length; i++ ) {
                        if ( file.id === self.files[i].id ) {
                            self.files[i].name = file.name;
                            self.files[i].shared = file.shared;
                        }
                    }
                }
                doneFunc( response.status );
            }
        );
    },

    // function doneFunc( status, errorString )
    deleteFile: function( id, doneFunc )
    {
        var self = this;
        this.makeAjaxRequest("index.php",
            {
                "type": "deletefile",
                "id": id
            },

            function(response) {
                if ( response.status === 'ok' ) {
                    for( var i = 0; i < self.files.length; i++ ) {
                        if ( id === self.files[i].id ) {
                            // use == to avoid string/int issues.
                            self.files.splice( i, 1 );
                            break;
                        }
                    }
                    
                }
                doneFunc( response.status );
            }
        );
    },

    // function doneFunc( status, errorString, data )
    readFile: function( id, doneFunc )
    {
        this.makeAjaxRequest("index.php",
            {
                "type": "readfile",
                "id": id
            },

            function(response) {
                if ( response.status === "ok" ) {
                    doneFunc( response.status, 
                        {
                            id: response.json["id"],
                            name: response.json["name"], 
                            data: response.json["data"],
                            shared: response.json["shared"] === "1"
                        });
                } else {
                    doneFunc( response.status, null );
                }
            }
        );
    }
};

/** @constructor */
function BitWriter()
{
    // encodes as URL-BASE64
    this.str = "";
    this.partial = 0;
    this.partialSize = 0;
    this.table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
    this.addBits = function( bits, size ) 
    {
        this.partial = (this.partial << size) | bits;
        this.partialSize += size;
        while ( this.partialSize >= 6 ) {
            this.str += this.table.charAt((this.partial >> 
                                           (this.partialSize - 6)) & 0x3f);
            this.partialSize -= 6;
        }
    };
    this.finish = function() {
        if ( this.partialSize ) {
            this.str += this.table.charAt( 
                ( this.partial << ( 6 - this.partialSize ) ) & 0x3f );
            this.partialSize = 0;
            this.partial = 0;
        }
    };
}

/** @constructor */
function BitWriter2()
{
    this.str = "";
    this.partial = 0;
    this.partialSize = 0;
    this.addBits = function( bits, size ) 
    {
        this.partial = (this.partial << size) | bits;
        this.partialSize += size;
        while ( this.partialSize >= 8 ) {
            this.str += String.fromCharCode((this.partial >> 
                                           (this.partialSize - 8)) & 0xff);
            this.partialSize -= 8;
        }
    };
    this.finish = function() {
        if ( this.partialSize ) {
            this.str += this.table.charAt( 
                ( this.partial << ( 8 - this.partialSize ) ) & 0xff );
            this.partialSize = 0;
            this.partial = 0;
        }
    };
}

function encodeBase64(str)
{
    var writer = new BitWriter();
    for (var n = 0; n < str.length; n++) {
        writer.addBits( str.charCodeAt( n ), 8 );
    }

    writer.finish();

    return writer.str;
}

function decodeBase64( input )
{
    var writer = new BitWriter2();
    for( var i = 0; i < input.length; i++ ) {
        var ch = input.charCodeAt(i);
        if ( ch >= 65 && ch <= 90 ) {
            writer.addBits( ch - 65, 6 );
        } else if ( ch >= 97 && ch <= 122 ) {
            writer.addBits( 26 + (ch - 97), 6 );
        } else if ( ch >= 48 && ch <= 57 ) {
            writer.addBits( 52 + (ch - 48), 6 );
        } else if ( ch ===  45) {
            writer.addBits( 62, 6 );
        } else if ( ch === 95 ) {
            writer.addBits( 63, 6 );
        }
    }

    return writer.str;
}

function encodeUtf8(string) {
    // fronm http://www.webtoolkit.info/
    string = string.replace(/\r\n/g,"\n");
    var utftext = "";

    for (var n = 0; n < string.length; n++) {

        var c = string.charCodeAt(n);

        if (c < 128) {
            utftext += String.fromCharCode(c);
        }
        else if((c > 127) && (c < 2048)) {
            utftext += String.fromCharCode((c >> 6) | 192);
            utftext += String.fromCharCode((c & 63) | 128);
        }
        else {
            utftext += String.fromCharCode((c >> 12) | 224);
            utftext += String.fromCharCode(((c >> 6) & 63) | 128);
            utftext += String.fromCharCode((c & 63) | 128);
        }

    }

    return utftext;
}

function decodeUtf8(utftext) 
{
    var string = "";
    var i = 0;
    var c = 0, c2 = 0,c3;

    while ( i < utftext.length ) {

        c = utftext.charCodeAt(i);

        if (c < 128) {
            string += String.fromCharCode(c);
            i++;
        }
        else if((c > 191) && (c < 224)) {
            c2 = utftext.charCodeAt(i+1);
            string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
            i += 2;
        }
        else {
            c2 = utftext.charCodeAt(i+1);
            c3 = utftext.charCodeAt(i+2);
            string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
            i += 3;
        }

    }

    return string;
}

function encodeNumber(num)
{
    // encodes a number in only as many bytes as required, 7 bits at a time.
    // bit 8 is used to indicate whether another byte follows.
    if ( num >= 0x3FFF ) {
        return String.fromCharCode( 0x80 | ( (num >> 14) & 0x7f ) ) +
               String.fromCharCode( 0x80 | ( (num >>  7) & 0x7f ) ) + 
               String.fromCharCode( num & 0x7f );
    } else if ( num >= 0x7F ) {            
        return String.fromCharCode( 0x80 | ( (num >>  7) & 0x7f ) ) + 
               String.fromCharCode( num & 0x7f );
    } else {
        return String.fromCharCode( num );
    }
}


function decodeNumber( str, pos )
{
    // returns [number,newpos]

    var ret = 0;
    while ( pos < str.length ) {
        var ch = str.charCodeAt( pos );
        pos += 1;
        ret = ( ret << 7 ) | ( ch & 0x7f );
        if ( ( ch & 0x80 ) === 0 ) {
            break;
        }
    }

    return [ret,pos];
}

function encodeLz77( input )
{
    var MinStringLength = 4;

    var output = "";
    var pos = 0;
    var hash = {};

    // set last pos to just after the last chunk.
    var lastPos = input.length - MinStringLength;

    for ( var i = MinStringLength; i < input.length; i++ ) {
        var subs = input.substr(i-MinStringLength, MinStringLength);
        if ( hash[subs] === undefined ) {
            hash[subs] = [];
        }
        hash[subs].push( i-MinStringLength );
        //document.write("subs[" + subs + "]=" + (pos - MinStringLength) + "<br>");
    }
    alert("Done hash");

    // loop until pos reaches the last chunk.
    while (pos < lastPos) {

        // search start is the current position minus the window size, capped
        // at the beginning of the string.
        var matchLength = MinStringLength;
        var foundMatch = false;
        var bestMatch = {distance: 0, length: 0};
        var prefix = input.substr(pos, MinStringLength);
        var matches = hash[prefix];
        
        // loop until the end of the matched region reaches the current
        // position.
        //while ((searchStart + matchLength) < pos) {
        if ( matches !== undefined ) {
            for ( i = 0; i < matches.length; i++ ) {
                var searchStart = matches[i];
                if ( searchStart + matchLength >= pos ) {
                    break;
                }
                
                while( searchStart + matchLength < pos ) {
                    // check if string matches.
                    var isValidMatch = (
                            (input.substr(searchStart, matchLength) === input.substr(pos, matchLength))
                            );
                    if (isValidMatch) {
                        // we found at least one match. try for a larger one.
                        var realMatchLength = matchLength;
                        matchLength++;
                        if (foundMatch && (realMatchLength > bestMatch.length)) {
                            bestMatch.distance = pos - searchStart - realMatchLength;
                            bestMatch.length = realMatchLength;
                        }
                        foundMatch = true;
                    } else {
                        break;
                    }
                }
            }
        }

        if (bestMatch.length) {
            output += String.fromCharCode( 0 ) + 
                encodeNumber(bestMatch.distance) +
                encodeNumber(bestMatch.length);

            pos += bestMatch.length;
        } else {
            if (input.charCodeAt(pos) !== 0) {
                output += input.charAt(pos);
            } else {
                output += String.fromCharCode( 0 ) + 
                    String.fromCharCode( 0 );
            }
            pos++;
        }
    }
    return output + input.slice(pos).replace(/\u0000/g, "\u0000\u0000");
}

function decodeLz77( input )
{
    var output = "";
    var pos = 0;

    while ( pos < input.length ) {
        var ch = input.charAt(pos);
        if ( ch !== "\u0000" ) {
            output += ch;
            pos++;
        } else {
            var nextChar = input.charAt(pos + 1);
            if (nextChar !== "\u0000") {
                pos++;
                var distance, length;
                var result = decodeNumber( input, pos );
                distance = result[0];
                pos = result[1];
                result = decodeNumber(input, pos);
                length = result[0];
                pos = result[1];
                output += output.substr(output.length - distance - length, length);
            } else {
                output += '\u0000';
                pos += 2;
            }
        }
    }
    return output;
}

function compress( str )
{
    return encodeBase64( encodeLz77( encodeUtf8( str ) ) );
}

function uncompress( str ) 
{
    return decodeUtf8( decodeLz77( decodeBase64( str ) ) );
}
