var opensocial = opensocial || {};

// ============================================================
// MODEL DEFINITION
// ============================================================

// Helper constructor
opensocial._bindData = function (_this, fields, dataIn, readOnly) {
    var data = {};
    if (typeof dataIn == 'object' && dataIn != null) {
        for(var i in fields) {
            var key = fields[i];
            if (typeof dataIn[key] != 'undefined') {
                data[key] = dataIn[key];
            }
        }
    }

    _this.getField = function (key, opt_params) {
        var value = data[key];
        return (typeof value == 'undefined')? null: value;
    };

    if (!readOnly) {
        _this.setField = function (key, value) {
            if (typeof fields[key] != 'unavailable') {
                data[key] = value;
            }
        }
    }

    return _this;
};

// ============================================================
// Activity
// ============================================================
opensocial.Activity = function (data) {
    opensocial._bindData(this, opensocial.Activity.Field, data, false);
    this.getId = function () {
        return data.id;
    };
};

opensocial.Activity.Field = {
    APP_ID: 'appId',
    BODY: 'body',
    BODY_ID: 'bodyId',
    EXTERNAL_ID: 'externalId',
    ID: 'id',
    MEDIA_ITEMS: 'mediaItems',
    POSTED_TIME: 'postedTime',
    PRIORITY: 'priority',
    STREAM_FAVICON_URL: 'streamFaviconUrl',
    STREAM_SOURCE_URL: 'streamSourceUrl', 
    STREAM_TITLE: 'streamTitle',
    STREAM_URL: 'streamUrl',
    TEMPLATE_PARAMS: 'templateParams',
    TITLE: 'title',
    TITLE_ID: 'titleId',
    URL: 'url',
    USER_ID: 'userId'
};


// ============================================================
// Address
// ============================================================
opensocial.Address = function (data) {
    opensocial._bindData(this, opensocial.Address.Field, data, true);
};

opensocial.Address.Field = {
    COUNTRY: 'country',
    EXTENDED_ADDRESS: 'extendedAddress',
    LATITUDE: 'latitude',
    LOCALITY: 'locality',
    LONGITUDE: 'longitude',
    PO_BOX: 'poBox',
    POSTAL_CODE: 'postalCode',
    REGION: 'region',
    STREET_ADDRESS: 'streetAddress',
    TYPE: 'type',
    UNSTRUCTURED_ADDRESS: 'unstructuredAddress'    
};


// ============================================================
// Album
// ============================================================
opensocial.Album = function (data) {
    opensocial._bindData(this, opensocial.Album.Field, data, false);
};

opensocial.Album.Field = {
    DESCRIPTION: 'description',
    ID: 'id',
    LOCATION: 'location',
    MEDIA_ITEM_COUNT: 'mediaItemCount',
    MEDIA_MIME_TYPE: 'mediaMimeType',
    MEDIA_TYPE: 'mediaType',
    OWNER_ID: 'ownerId',
    THUMBNAIL_URL: 'thumbnailUrl',
    TITLE: 'title'
};


// ============================================================
// BodyType
// ============================================================
opensocial.BodyType = function (data) {
    opensocial._bindData(this, opensocial.BodyType.Field, data, true);
};

opensocial.BodyType.Field = {
    BUILD: 'build',
    EYE_COLOR: 'eyeColor',
    HAIR_COLOR: 'hairColor',
    HEIGHT: 'height',
    WEIGHT: 'weight'
};


// ============================================================
// CreateActivity
// ============================================================
opensocial.CreateActivityPriority = {
    HIGH: 'HIGH',
    LOW: 'LOW'
};


// ============================================================
// Email
// ============================================================
opensocial.Email = function (data) {
    opensocial._bindData(this, opensocial.Email.Field, data, true);
};

opensocial.Email.Field = {
    ADDRESS: 'address',
    TYPE: 'type'
};


// ============================================================
// Enum
// ============================================================
opensocial.Enum = function (key, value) {
    if (typeof key != 'string') {
        key = null;
    }
    if (typeof value != 'string') {
        value = key;
    }

    this.getKey = function () {
        return key;
    };

    this.getDisplayValue = function () {
        return value;
    };
};

opensocial.Enum.Drinker = {
    HEAVILY: 'HEAVILY',
    NO: 'NO',
    OCCASIONALLY: 'OCCASIONALLY',
    QUIT: 'QUIT',
    QUITTING: 'QUITTING',
    REGULARLY: 'REGULARLY',
    SOCIALLY: 'SOCIALLY',
    YES: 'YES'
};

opensocial.Enum.Gender = {
    FEMALE: 'FEMALE',
    MALE: 'MALE'
};

opensocial.Enum.LookingFor = {
    ACTIVITY_PARTNERS: 'ACTIVITY_PARTNERS',
    DATING: 'DATING',
    FRIENDS: 'FRIENDS',
    NETWORKING: 'NETWORKING',
    RANDOM: 'RANDOM',
    RELATIONSHIP: 'RELATIONSHIP'
};

opensocial.Enum.Presence = {
    AWAY: 'AWAY',
    CHAT: 'CHAT',
    DND: 'DND',
    OFFLINE: 'OFFLINE',
    ONLINE: 'ONLINE',
    XA: 'XA'
};

opensocial.Enum.Smoker = {
    HEAVILY: 'HEAVILY',
    NO: 'NO',
    OCCASIONALLY: 'OCCASIONALLY',
    QUIT: 'QUIT',
    QUITTING: 'QUITTING',
    REGULARLY: 'REGULARLY',
    SOCIALLY: 'SOCIALLY',
    YES: 'YES'
};


// ============================================================
// EscapeType
// ============================================================
opensocial.EscapeType = {
    HTML_ESCAPE: 'htmlEscape',
    NONE: 'none'
};


// ============================================================
// IdSpec
// ============================================================
opensocial.IdSpec = function (data) {
    opensocial._bindData(this, opensocial.IdSpec.Field, data, false);
};

opensocial.IdSpec.Field = {
    GROUP_ID: 'groupId',
    NETWORK_DISTANCE: 'networkDistance',
    USER_ID: 'userId'
};

opensocial.IdSpec.GroupId = {
    ALL: 'ALL',
    FRIENDS: 'FRIENDS',
    SELF: 'SELF'
};

opensocial.IdSpec.PersonId = {
    OWNER: 'OWNER',
    VIEWER: 'VIEWER'
};


// ============================================================
// MediaItem
// ============================================================
opensocial.MediaItem = function (data) {
    opensocial._bindData(this, opensocial.MediaItem.Field, data, false);
};

opensocial.MediaItem.Field = {
    ALBUM_ID: 'albumId',
    CREATED: 'created',
    DESCRIPTION: 'description',
    DURATION: 'duration',
    FILE_SIZE: 'fileSize',
    ID: 'id', 
    LANGUAGE: 'language',
    LAST_UPDATED: 'lastUpdated',
    LOCATION: 'location',
    MIME_TYPE: 'mimeType',
    NUM_COMMENTS: 'numComments',
    NUM_VIEWS: 'numViews',
    NUM_VOTES: 'numVotes',
    RATING: 'rating',
    START_TIME: 'startTime',
    TAGGED_PEOPLE: 'taggedPeople',
    TAGS: 'tags',
    THUMBNAIL_URL: 'thumbnailUrl',
    TITLE: 'title',
    TYPE: 'type',
    URL: 'url'
};

opensocial.MediaItem.Type = {
    AUDIO: 'audio',
    IMAGE: 'image',
    VIDEO: 'video'
};


// ============================================================
// Messages
// ============================================================
opensocial.Message = function (data) {
    opensocial._bindData(this, opensocial.Message.Field, data, false);
};

opensocial.Message.Field = {
    APP_URL: 'appUrl',
    BODY: 'body',
    BODY_ID: 'bodyId',
    COLLECTION_IDS: 'collectionIds',
    ID: 'id',
    IN_REPLY_TO: 'inReplyTo',
    RECIPIENTS: 'recipients',
    REPLIES: 'replies',
    SENDER_ID: 'senderId',
    STATUS: 'status',
    TIME_SENT: 'timeSent',
    TITLE: 'title',
    TITLE_ID: 'titleId',
    TYPE: 'type',
    UPDATED: 'updated',
    URLS: 'urls'
};

opensocial.MessageCollection = function (data) {
    opensocial._bindData(this, opensocial.MessageCollection.Field, data, false);
};

opensocial.MessageCollection.Field = {
    ID: 'id',
    TITLE: 'title',
    TOTAL: 'total',
    UNREAD: 'unread',
    UPDATED: 'updated',
    URLS: 'urls'
};


// ============================================================
// Name
// ============================================================
opensocial.Name = function (data) {
    opensocial._bindData(this, opensocial.Name.Field, data, false);
};

opensocial.Name.Field = {
    ADDITIONAL_NAME: 'additionalName',
    FAMILY_NAME: 'familyName',
    GIVEN_NAME: 'givenName',
    HONORIFIC_PREFIX: 'honorificPrefix',
    HONORIFIC_SUFFIX: 'honorificSuffix',
    UNSTRUCTURED: 'unstructured'
};


// ============================================================
// NavigationParameters
// ============================================================
opensocial.NavigationParameters = function (data) {
    opensocial._bindData(this, opensocial.NavigationParameters.Field, data, false);
};

opensocial.NavigationParameters.Field = {
    OWNER: 'owner',
    PARAMETERS: 'parameters',
    VIEW: 'view'
};

opensocial.NavigationParameters.DestinationType = {
    RECIPIENT_DESTINATION: 'recipientDestination',
    VIEWER_DESTINATION: 'viewerDestination'
};


// ============================================================
// Organization
// ============================================================
opensocial.Organization = function (data) {
    opensocial._bindData(this, opensocial.Organization.Field, data, true);
};

opensocial.Organization.Field = {
    ADDRESS: 'address',
    DESCRIPTION: 'description',
    END_DATE: 'endDate',
    FIELD: 'field',
    NAME: 'name',
    SALARY: 'salary',
    START_DATE: 'startDate',
    SUB_FIELD: 'subField',
    TITLE: 'title',
    WEBPAGE: 'webpage'
};


// ============================================================
// Permission
// ============================================================
opensocial.Permission = {
    VIEWER: 'viewer'
};


// ============================================================
// Person
// ============================================================
opensocial.Person = function (data) {
    opensocial._bindData(this, opensocial.Person.Field, data, true);

    this.getAppData = function (key) {
        return null; // TODO
    };

    this.getDisplayName = function () {
        return data.displayName;
    };

    this.getId = function () {
        return data.id;
    };

    this.isOwner = function () {
        return false; // TODO
    };

    this.isViewer = function () {
        return false; // TODO
    };
};

opensocial.Person.Field = {
    ABOUT_ME: 'aboutMe',
    ACTIVITIES: 'activities',
    ADDRESSES: 'addresses',
    AGE: 'age',
    BODY_TYPE: 'bodyType',
    BOOKS: 'books',
    CARS: 'cars',
    CHILDREN: 'children',
    CURRENT_LOCATION: 'currentLocation',
    DATE_OF_BIRTH: 'dateOfBirth',
    DRINKER: 'drinker',
    EMAILS: 'emails',
    ETHNICITY: 'ethnicity',
    FASHION: 'fashion',
    FOOD: 'food',
    GENDER: 'gender',
    HAPPIEST_WHEN: 'happiestWhen',
    HAS_APP: 'hasApp',
    HEROES: 'heroes',
    HUMOR: 'humor',
    ID: 'id',
    INTERESTS: 'interests',
    JOB_INTERESTS: 'jobInterests',
    JOBS: 'jobs',
    LANGUAGES_SPOKEN: 'languagesSpoken',
    LIVING_ARRANGEMENT: 'livingArrangement',
    LOOKING_FOR: 'lookingFor',
    MOVIES: 'movies',
    MUSIC: 'music',
    NAME: 'name',
    NETWORK_PRESENCE: 'networkPresence',
    NICKNAME: 'nickname',
    PETS: 'pets',
    PHONE_NUMBERS: 'phoneNumbers',
    POLITICAL_VIEWS: 'politicalViews',
    PROFILE_SONG: 'profileSong',
    PROFILE_URL: 'profileUrl',
    PROFILE_VIDEO: 'profileVideo',
    QUOTES: 'quotes',
    RELATIONSHIP_STATUS: 'relationshipStatus',
    RELIGION: 'religion',
    ROMANCE: 'romance',
    SCARED_OF: 'scaredOf',
    SCHOOLS: 'schools',
    SEXUAL_ORIENTATION: 'sexualOrientation',
    SMOKER: 'smoker',
    SPORTS: 'sports',
    STATUS: 'status',
    TAGS: 'tags',
    THUMBNAIL_URL: 'thumbnailUrl',
    TIME_ZONE: 'timeZone',
    TURN_OFFS: 'turnOffs',
    TURN_ONS: 'turnOns',
    TV_SHOWS: 'tvShows',
    URLS: 'urls'
};


// ============================================================
// Phone
// ============================================================
opensocial.Phone = function (data) {
    opensocial._bindData(this, opensocial.Phone.Field, data, true);
};

opensocial.Phone.Field = {
    NUMBER: 'number',
    TYPE: 'type'
};


// ============================================================
// Url
// ============================================================
opensocial.Url = function (data) {
    opensocial._bindData(this, opensocial.Url.Field, data, true);
};

opensocial.Url.Field = {
    ADDRESS: 'address',
    LINK_TEXT: 'linkText',
    TYPE: 'type'
};




 // ============================================================
// UTILS
// ============================================================

// ============================================================
// Collection
// ============================================================
opensocial.Collection = function (pager, opt_constructor) {
    var entries;
    var totalResults = 0;
    var startIndex = 0;

    if (typeof pager.totalResults == 'number') {
        totalResults = pager.totalResults;
        startIndex = pager.startIndex;
        entries = pager.entry;
    } else {
        entries = pager;
    }

    var size = entries.length;
    if (typeof opt_constructor == 'function') {
        for(var key in entries) {
            entries[key] = opt_constructor(entries[key]);
        }
    }

    this.asArray = function () {
        return entries.slice();
    };

    this.each = function (fn) {
        for(var key in entries) {
            fn(entries[key]);
        }
    };

    this.getById = function (id) {
        for(var key in data) {
            var e = entries[key];
            if (typeof e.id != 'undefined' && e.id == id) {
                return e;
            }
        }

        return null;
    };

    this.getOffset = function () {
        return startIndex;
    };

    this.getTotalSize = function () {
        return totalResults;
    };

    this.size = function () {
        return size;
    };
};




// ============================================================
// Environment
// ============================================================
opensocial.Environment = function () {
    this.getDomain = function () {
        var domain = window.location.hostname.toLowerCase();
        var prefix = domain.substr(0, 4);
        if (prefix == 'www.' || prefix == 'wap.') {
            domain = domain.substr(4);
        }
        return domain;
    };

    this.supportsField = function (objectType, fieldName) {
        return true;
    };
};

opensocial.Environment.ObjectType = {
    ACTIVITY: 'activity',
    ADDRESS: 'address',
    BODY_TYPE: 'bodyType',
    EMAIL: 'email',
    FILTER_TYPE: 'filterType',
    MEDIA_ITEM: 'mediaItem',
    MESSAGE: 'message',
    MESSAGE_TYPE: 'messageType',
    NAME: 'name',
    ORGANIZATION: 'organization',
    PERSON: 'person',
    PHONE: 'phone',
    SORT_ORDER: 'sortOrder',
    URL: 'url'
};

opensocial.getEnvironment = function () {
    return new opensocial.Environment();
};




// ============================================================
// DATA API
// ============================================================
opensocial.ResponseItem = function (originalRequest, data, errCode, errMessage) {
    this.getData = function () {
        return data;
    };

    this.getErrorCode = function () {
        return errCode;
    };

    this.getErrorMessage = function () {
        return errMessage;
    };

    this.getOriginalDataRequest = function () {
        return originalRequest;
    };

    this.hadError = function () {
        return errCode != null;
    };
};

opensocial.ResponseItem.Error = {
    BAD_REQUEST: 'BAD_REQUEST',
    FORBIDDEN: 'FORBIDDEN',
    INTERNAL_ERROR: 'INTERNAL_ERROR',
    LIMIT_EXCEEDED: 'LIMIT_EXCEEDED',
    NOT_IMPLEMENTED: 'NOT_IMPLEMENTED',
    UNAUTHORIZED: 'UNAUTHORIZED'
};

opensocial.DataResponse = function () {
    var items = {};
    var errItem = null;
    
    this.get = function (key) {
        var i = items[key];
        return (typeof i == 'undefined'? null: i);
    };

    this.getErrorMessage = function () {
        return errItem == null? null: "Error " + errItem.getErrorCode() + ": " + errItem.getErrorMessage();
    };

    this.hadError = function () {
        return errItem != null;
    };

    this._add = function (key, originalRequest, data, errCode, errMessage) {
        var item = new opensocial.ResponseItem(originalRequest, data, errCode, errMessage);
        items[key] = item;
        if (item.hadError()) {
            errItem = item;
        }
    };
};

opensocial.DataRequest = function () {
    var requests = {};
    var requestCount = 0;

    this.add = function (request, opt_key) {
        if (typeof request != 'object' || request == null) {
            return;
        }
        if (typeof opt_key == 'string') {
            if (typeof requests[opt_key] == 'undefined') {
                requestCount++;
            }
            requests[opt_key] = request;
        } else {
            requestCount++;
            request['__' + requestCount + '__'] = request;
        }
    };

    this.send = function (opt_callback) {
        if (typeof opt_callback != 'function') {
            opt_callback = null;
        }

        var reqs = requests;
        var count = requestCount;
        requests = {};
        requestCount = 0;

        var urlPrefix = 'http://' + window.location.host + '/api/rest/';
        var response = new opensocial.DataResponse();

        for (var key in reqs) {
            var request = reqs[key];

            $.ajax({
                url: (urlPrefix + request.getPath()),
                data: (typeof request.getData == 'function'? request.getData(): {}),
                type: (typeof request.getMethod == 'function'? request.getMethod(): 'GET'),
                cache: false,
                dataType: 'json',

                requestKey: key,
                requestReq: request,

                beforeSend: function(xhr) {
                    xhr.setRequestHeader("Authorization", "0000000000000000");
                    xhr.setRequestHeader("Content-Type", "application/json");
                },

                error: function (xhr, textStatus, errorThrown) {
                    count--;
                    var ecode = null;
                    switch(xhr.status) {
                    case 400: ecode = opensocial.ResponseItem.Error.INTERNAL_ERROR; break;
                    case 401: ecode = opensocial.ResponseItem.Error.UNAUTHORIZED; break;
                    case 403: ecode = opensocial.ResponseItem.Error.FORBIDDEN; break;
                    //case 405: ecode = opensocial.ResponseItem.Error.NOT_FOUND; break;
                    //case 409: ecode = opensocial.ResponseItem.Error.CONFLICT; break;
                    //case ???: ecode = opensocial.ResponseItem.Error.LIMIT_EXCEEDED; break;
                    case 501: ecode = opensocial.ResponseItem.Error.NOT_IMPLEMENTED; break;
                    default : ecode = opensocial.ResponseItem.Error.INTERNAL_ERROR;
                    }

                    response._add(this.requestKey, this.requestReq, null, ecode, xhr.responseText);
                    if (count == 0 && opt_callback != null) {
                        delete response._add;
                        opt_callback(response);
                    }
                },

                success: function (data, textStatus) {
                    count--;
                    if (typeof this.requestReq.toModel == 'function') {
                        data = this.requestReq.toModel(data); 
                    }
                                  
                    response._add(this.requestKey, this.requestReq, data, null, null);
                    if (count == 0 && opt_callback != null) {
                        delete response._add;
                        opt_callback(response);
                    }
                }
            });
        }
    };

    this.newFetchPersonRequest = function (idSpec, opt_params) {
        var userId = idSpec.getField('userId');
        if (userId == null || userId == 'VIEWER') {
            userId = '@me';
        } else if (userId == 'OWNER') {
            userId = '@owner'; // FIX ME
        }

        var groupId = idSpec.getField('groupId');
        if (groupId == null || groupId == 'SELF') {
            groupId = '@self';
        } else if (groupId == 'FRIENDS') {
            groupId = '@friends';
        } else if (groupId == 'ALL') {
            groupId = '@all';
        }

        var path = 'os/people/' + userId + '/' + groupId;  

        var data = {};
        if (typeof opt_params == 'object') {
            if (typeof opt_params.fields == 'string' && opt_params.fields != '@all') {
                data.fields = opt_params.fields;
            }
        }
        
        return {
            getPath: function () {
                return path;
            },

            getData: function () {
                return data;
            },

            toModel: function (response) {
                if (typeof response.totalResults == 'number') {
                    return new opensocial.Collection(response, function (e) {
                        return new opensocial.Person(e); 
                    });
                } else {
                    return new opensocial.Person(response);
                }
            }
        };
    };
};

opensocial.newDataRequest = function () {
    return new this.DataRequest();
};
