text:
<repo_name>steverichey/TinCan.NET<file_sep>/TinCan/LanguageMap.cs
/*
    Copyright 2014-2017 <NAME>

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
*/

using System.Collections.Generic;
using Newtonsoft.Json.Linq;
using TinCan.Json;

namespace TinCan
{
    /// <summary>
    /// Language map.
    /// </summary>
    public class LanguageMap : JsonModel
    {
        Dictionary<string, string> map;

        /// <summary>
        /// Initializes a new instance of the <see cref="T:TinCan.LanguageMap"/> class.
        /// </summary>
        public LanguageMap()
        {
            map = new Dictionary<string, string>();
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="T:TinCan.LanguageMap"/> class.
        /// </summary>
        /// <param name="map">Map.</param>
        public LanguageMap(Dictionary<string, string> map)
        {
            this.map = map;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="T:TinCan.LanguageMap"/> class.
        /// </summary>
        /// <param name="json">Json.</param>
        public LanguageMap(StringOfJSON json) : this(json.ToJObject()) { }

        /// <summary>
        /// Initializes a new instance of the <see cref="T:TinCan.LanguageMap"/> class.
        /// </summary>
        /// <param name="jobj">Jobj.</param>
        public LanguageMap(JObject jobj) : this()
        {
            foreach (var entry in jobj)
            {
                map.Add(entry.Key, (string)entry.Value);
            }
        }

        /// <summary>
        /// Gets a value indicating whether this <see cref="T:TinCan.LanguageMap"/> is empty.
        /// </summary>
        /// <value><c>true</c> if is empty; otherwise, <c>false</c>.</value>
		public bool IsEmpty
		{
            get
            {
                return map.Count <= 0;
            }
		}

        /// <inheritdoc />
        public override JObject ToJObject(TCAPIVersion version)
        {
            var result = new JObject();

            foreach (var entry in map)
            {
                result.Add(entry.Key, entry.Value);
            }

            return result;
        }

        /// <summary>
        /// Add the specified language and value to this map.
        /// </summary>]
        /// <param name="lang">Language related to this value.</param>
        /// <param name="value">Value related to the given language.</param>
        public void Add(string lang, string value)
        {
            map.Add(lang, value);
        }

        /// <summary>
        /// Returns a <see cref="T:System.String"/> that represents the current <see cref="T:TinCan.LanguageMap"/>.
        /// </summary>
        /// <returns>A <see cref="T:System.String"/> that represents the current <see cref="T:TinCan.LanguageMap"/>.</returns>
        public override string ToString()
        {
            return string.Format("[LanguageMap: IsEmpty={0}, Map={1}]", IsEmpty, map);
        }

		/// <summary>
		/// Defines the operation to use when casting from a JObject to this type.
		/// </summary>
		/// <returns>The JObject as this type.</returns>
		/// <param name="jobj">The JObject to cast.</param>
		public static explicit operator LanguageMap(JObject jobj)
        {
            return new LanguageMap(jobj);
        }
    }
}
<file_sep>/TinCan/ILRS.cs
/*
    Copyright 2014-2017 <NAME>

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
*/

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using TinCan.Documents;
using TinCan.LRSResponses;

namespace TinCan
{
    /// <summary>
    /// Defines the interface for an LRS.
    /// </summary>
    public interface ILRS
    {
        /// <summary>
        /// Retrieve information about this LRS.
        /// </summary>
        /// <returns>The response with information about this LRS.</returns>
        Task<AboutLRSResponse> About();

        /// <summary>
        /// Saves a statement in the LRS.
        /// </summary>
        /// <returns>Response with information about the saved statement.</returns>
        /// <param name="statement">Statement to save in the LRS.</param>
        Task<StatementLRSResponse> SaveStatement(Statement statement);

        /// <summary>
        /// Voids a statement in the LRS.
        /// </summary>
        /// <returns>Response with information about the voided statement.</returns>
        /// <param name="id">Identifier of statement to void.</param>
        /// <param name="agent">Agent of statement to void.</param>
        Task<StatementLRSResponse> VoidStatement(Guid id, Agent agent);

		/// <summary>
		/// Save several statements in the LRS.
		/// </summary>
		/// <returns>Response with information about the saved statements.</returns>
		/// <param name="statements">Statements to save in the LRS.</param>
		Task<StatementsResultLRSResponse> SaveStatements(List<Statement> statements);

		/// <summary>
		/// Retrieves a statement from the LRS.
		/// </summary>
		/// <returns>Response with information about the retrieved statement.</returns>
		/// <param name="id">Identifier of statement to retrieve.</param>
		Task<StatementLRSResponse> RetrieveStatement(Guid id);

		/// <summary>
		/// Retrieves a voided statement from the LRS.
		/// </summary>
		/// <returns>Response with information about the retrieved voided statement.</returns>
		/// <param name="id">Identifier of voided statement to retrieve.</param>
		Task<StatementLRSResponse> RetrieveVoidedStatement(Guid id);

		/// <summary>
		/// Queries the LRS for statements.
		/// </summary>
		/// <returns>Response with information about the queried statements.</returns>
		/// <param name="query">Query to send to the LRS.</param>
		Task<StatementsResultLRSResponse> QueryStatements(StatementsQuery query);

		/// <summary>
		/// Get more statements from the LRS using a previous result.
		/// </summary>
		/// <returns>Response with information about the additional statements.</returns>
		/// <param name="result">Result of a previous operation with ID for retrieving more results.</param>
		Task<StatementsResultLRSResponse> MoreStatements(StatementsResult result);

        /// <summary>
        /// Retrieves state identifiers for the given paraemeters.
        /// </summary>
        /// <returns>Response with information about the state identifiers.</returns>
        /// <param name="activity">Activity related to the state identifiers.</param>
        /// <param name="agent">Agent related to the state identifiers.</param>
        /// <param name="registration">Registration related to the state identifiers.</param>
        Task<ProfileKeysLRSResponse> RetrieveStateIds(Activity activity, Agent agent, Guid? registration = null);

		/// <summary>
		/// Retrieves a state from the LRS.
		/// </summary>
		/// <returns>Response with information about the state.</returns>
		/// <param name="id">Identifier of the state to retrieve.</param>
		/// <param name="activity">Activity related to the state to retrieve.</param>
		/// <param name="agent">Agent related to the state to retrieve.</param>
		/// <param name="registration">Registration related to the state to retrieve.</param>
		Task<StateLRSResponse> RetrieveState(string id, Activity activity, Agent agent, Guid? registration = null);

		/// <summary>
		/// Saves a state in the LRS.
		/// </summary>
		/// <returns>Response with information about the saved state.</returns>
		/// <param name="state">The state to save.</param>
		Task<LRSResponse> SaveState(StateDocument state);

		/// <summary>
		/// Deletes a state from the LRS.
		/// </summary>
		/// <returns>Response with information about the deleted state.</returns>
		/// <param name="state">The state to delete.</param>
		Task<LRSResponse> DeleteState(StateDocument state);

		/// <summary>
		/// Clears the state from the LRS.
		/// </summary>
		/// <returns>Response with information about the cleared state.</returns>
		/// <param name="activity">Activity related to the state.</param>
		/// <param name="agent">Agent related to the state.</param>
		/// <param name="registration">Registration identifier related to the state.</param>
		Task<LRSResponse> ClearState(Activity activity, Agent agent, Guid? registration = null);

        /// <summary>
        /// Retrieves the activity profile IDs from the LRS.
        /// </summary>
        /// <returns>Response with information about the activity profile IDs.</returns>
        /// <param name="activity">Activity with profile IDs to retrieve.</param>
        Task<ProfileKeysLRSResponse> RetrieveActivityProfileIds(Activity activity);

        /// <summary>
        /// Retrieves an activity profile from the LRS.
        /// </summary>
        /// <returns>Response with information about the activity profile.</returns>
        /// <param name="id">Identifier of the activity profile to retrieve.</param>
        /// <param name="activity">Activity with profile information to retrieve.</param>
        Task<ActivityProfileLRSResponse> RetrieveActivityProfile(string id, Activity activity);

        /// <summary>
        /// Saves an activity profile in the LRS.
        /// </summary>
        /// <returns>Response with information about the saved activity profile.</returns>
        /// <param name="profile">Profile to save.</param>
        Task<LRSResponse> SaveActivityProfile(ActivityProfileDocument profile);

		/// <summary>
		/// Deletes an activity profile from the LRS.
		/// </summary>
		/// <returns>Response with information about the deleted activity profile.</returns>
		/// <param name="profile">Profile to delete.</param>
		Task<LRSResponse> DeleteActivityProfile(ActivityProfileDocument profile);

        /// <summary>
        /// Retrieves agent profile IDs from the LRS.
        /// </summary>
        /// <returns>Response with information about the agent profile identifiers.</returns>
        /// <param name="agent">Agent with profile IDs to retrieve.</param>
        Task<ProfileKeysLRSResponse> RetrieveAgentProfileIds(Agent agent);

		/// <summary>
		/// Retrieves an agent profile from the LRS.
		/// </summary>
		/// <returns>Response with information about the retrieved agent profile.</returns>
		/// <param name="id">Identifier of the agent profile to retrieve.</param>
		/// <param name="agent">Agent with profile to retrieve.</param>
		Task<AgentProfileLRSResponse> RetrieveAgentProfile(string id, Agent agent);

		/// <summary>
		/// Saves the agent profile in the LRS.
		/// </summary>
		/// <returns>Response with information about the saved agent profile.</returns>
		/// <param name="profile">Profile to save.</param>
		Task<LRSResponse> SaveAgentProfile(AgentProfileDocument profile);

		/// <summary>
		/// Saves the agent profile in the LRS and overwrites any previous version of the agent profile.
		/// </summary>
		/// <returns>Response with information about the saved agent profile.</returns>
		/// <param name="profile">Profile to force save.</param>
		Task<LRSResponse> ForceSaveAgentProfile(AgentProfileDocument profile);

		/// <summary>
		/// Deletes an agent profile from the LRS.
		/// </summary>
		/// <returns>Response with information about the deleted agent profile.</returns>
		/// <param name="profile">Profile to delete.</param>
		Task<LRSResponse> DeleteAgentProfile(AgentProfileDocument profile);
    }
}
<file_sep>/TinCan/StatementRef.cs
/*
    Copyright 2014-2017 <NAME>

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
*/

using System;
using Newtonsoft.Json.Linq;
using TinCan.Json;

namespace TinCan
{
    /// <summary>
    /// An object describing a reference to another statement.
    /// </summary>
    public class StatementRef : JsonModel, IStatementTarget
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="T:TinCan.StatementRef"/> class.
        /// </summary>
        public StatementRef() {}

        /// <summary>
        /// Initializes a new instance of the <see cref="T:TinCan.StatementRef"/> class.
        /// </summary>
        /// <param name="id">Identifier.</param>
        public StatementRef(Guid id)
        {
            Id = id;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="T:TinCan.StatementRef"/> class.
        /// </summary>
        /// <param name="json">Json.</param>
        public StatementRef(StringOfJSON json): this(json.ToJObject()) {}

        /// <summary>
        /// Initializes a new instance of the <see cref="T:TinCan.StatementRef"/> class.
        /// </summary>
        /// <param name="jobj">Jobj.</param>
        public StatementRef(JObject jobj)
        {
            if (jobj["id"] != null)
            {
                Id = new Guid(jobj.Value<string>("id"));
            }
        }

        /// <summary>
        /// Gets the type of the object.
        /// </summary>
        /// <value>The type of the object.</value>
        public string ObjectType
        {
            get
            {
                return TypeName;
            }
        }

        /// <summary>
        /// Gets or sets the identifier of the referenced statement.
        /// </summary>
        /// <value>The identifier.</value>
        public Guid? Id { get; set; }

        /// <inheritdoc />
        public override JObject ToJObject(TCAPIVersion version)
        {
            var result = new JObject
            {
                { "objectType", ObjectType }
            };

            if (Id != null)
            {
                result.Add("id", Id.ToString());
            }

            return result;
        }

        /// <summary>
        /// Returns a <see cref="T:System.String"/> that represents the current <see cref="T:TinCan.StatementRef"/>.
        /// </summary>
        /// <returns>A <see cref="T:System.String"/> that represents the current <see cref="T:TinCan.StatementRef"/>.</returns>
        public override string ToString()
        {
            return string.Format("[StatementRef: ObjectType={0}, Id={1}]",
                                 ObjectType, Id);
        }

		/// <summary>
		/// The type of the object.
		/// </summary>
		public static readonly string TypeName = nameof(StatementRef);

		/// <summary>
		/// Defines the operation to use when casting from a JObject to this type.
		/// </summary>
		/// <returns>The JObject as this type.</returns>
		/// <param name="jobj">The JObject to cast.</param>
		public static explicit operator StatementRef(JObject jobj)
        {
            return new StatementRef(jobj);
        }
    }
}
<file_sep>/TinCan/json/JsonModel.cs
/*
    Copyright 2014-2017 <NAME>

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
*/

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace TinCan.Json
{
    /// <summary>
    /// Base implementation of the JSON model interface.
    /// </summary>
    public abstract class JsonModel : IJsonModel
    {
		/// <summary>
        /// Implementing classes should use this to convert data to JObject format.
        /// Other methods in this abstract class will use this implementation.
        /// </summary>
        /// <returns>The implementing object as a JObject.</returns>
        /// <param name="version">Version of the API to use.</param>
		public abstract JObject ToJObject(TCAPIVersion version);

		/// <inheritdoc />
		public JObject ToJObject()
        {
            return ToJObject(TCAPIVersion.Latest);
        }

		/// <inheritdoc />
		public string ToJSON(TCAPIVersion version, bool pretty = false)
        {
            var formatting = Formatting.None;

            if (pretty)
            {
                formatting = Formatting.Indented;
            }

            return JsonConvert.SerializeObject(ToJObject(version), formatting);
        }

		/// <inheritdoc />
		public string ToJSON(bool pretty = false)
        {
            return ToJSON(TCAPIVersion.Latest, pretty);
        }

        /// <summary>
        /// Returns a <see cref="T:System.String"/> that represents the current <see cref="T:TinCan.Json.JsonModel"/>.
        /// </summary>
        /// <returns>A <see cref="T:System.String"/> that represents the current <see cref="T:TinCan.Json.JsonModel"/>.</returns>
        public override string ToString()
        {
            return ToJSON();
        }
    }
}
<file_sep>/TinCan/StatementsQueryResultFormat.cs
/*
    Copyright 2014-2017 <NAME>

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
*/

namespace TinCan
{
    /// <summary>
    /// Statements query result format enumeration.
    /// </summary>
    public sealed class StatementsQueryResultFormat
    {
        readonly string text;

        /// <summary>
        /// Initializes a new instance of the <see cref="T:TinCan.StatementsQueryResultFormat"/> class.
        /// </summary>
        /// <param name="value">Value.</param>
        StatementsQueryResultFormat(string value)
        {
            text = value;
        }

        /// <summary>
        /// Returns a <see cref="T:System.String"/> that represents the current <see cref="T:TinCan.StatementsQueryResultFormat"/>.
        /// </summary>
        /// <returns>A <see cref="T:System.String"/> that represents the current <see cref="T:TinCan.StatementsQueryResultFormat"/>.</returns>
        public override string ToString()
        {
            return text;
        }

		/// <summary>
		/// Only include minimum information necessary in Agent, Activity, Verb and Group Objects to identify them.
        /// For Anonymous Groups this means including the minimum information needed to identify each member.
		/// </summary>
		public static readonly StatementsQueryResultFormat Ids = new StatementsQueryResultFormat("ids");

		/// <summary>
		/// Return Agent, Activity, Verb and Group Objects populated exactly as they were when the Statement was received.
        /// An LRS requesting Statements for the purpose of importing them would use a format of "exact" in order to maintain Statement Immutability.
		/// </summary>
		public static readonly StatementsQueryResultFormat Exact = new StatementsQueryResultFormat("exact");

		/// <summary>
		/// Return Activity Objects and Verbs populated with the canonical definition of the Activity Objects and Display of the Verbs as determined by the LRS, after applying language filtering, and return the original Agent and Group Objects as in "exact" mode.
		/// </summary>
		public static readonly StatementsQueryResultFormat Canonical = new StatementsQueryResultFormat("canonical");
    }
}
<file_sep>/TinCan/SubStatement.cs
/*
    Copyright 2014-2017 <NAME>

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
*/

using Newtonsoft.Json.Linq;
using TinCan.Json;

namespace TinCan
{
    /// <summary>
    /// Sub statement.
    /// </summary>
    public class SubStatement : StatementBase, IStatementTarget
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="T:TinCan.SubStatement"/> class.
        /// </summary>
        public SubStatement() {}

        /// <summary>
        /// Initializes a new instance of the <see cref="T:TinCan.SubStatement"/> class.
        /// </summary>
        /// <param name="json">Json.</param>
        public SubStatement(StringOfJSON json): this(json.ToJObject()) {}

        /// <summary>
        /// Initializes a new instance of the <see cref="T:TinCan.SubStatement"/> class.
        /// </summary>
        /// <param name="jobj">Jobj.</param>
        public SubStatement(JObject jobj) : base(jobj) { }

        /// <summary>
        /// Tos the JO bject.
        /// </summary>
        /// <returns>The JO bject.</returns>
        /// <param name="version">Version.</param>
        public override JObject ToJObject(TCAPIVersion version)
        {
            var result = base.ToJObject(version);

            result.Add("objectType", ObjectType);

            return result;
		}

		/// <summary>
		/// Gets the type of the object.
		/// </summary>
		/// <value>The type of the object.</value>
		public string ObjectType
		{
			get
			{
				return TypeName;
			}
		}

		/// <summary>
		/// Returns a <see cref="T:System.String"/> that represents the current <see cref="T:TinCan.SubStatement"/>.
		/// </summary>
		/// <returns>A <see cref="T:System.String"/> that represents the current <see cref="T:TinCan.SubStatement"/>.</returns>
		public override string ToString()
		{
			return string.Format("[SubStatement: Actor={0}, Verb={1}, Target={2}, Result={3}, Context={4}, Timestamp={5}]",
                                 Actor, Verb, Target, Result, Context, Timestamp);
		}

        /// <summary>
        /// The name of the type.
        /// </summary>
        public static string TypeName = nameof(SubStatement);

		/// <summary>
		/// Defines the operation to use when casting from a JObject to this type.
		/// </summary>
		/// <returns>The JObject as this type.</returns>
		/// <param name="jobj">The JObject to cast.</param>
		public static explicit operator SubStatement(JObject jobj)
        {
            return new SubStatement(jobj);
        }
    }
}
<file_sep>/TinCan/json/IJsonModel.cs
/*
    Copyright 2014-2017 <NAME>

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
*/

using Newtonsoft.Json.Linq;

namespace TinCan.Json
{
    /// <summary>
    /// Defines an object that can be converted to a JObject or JSON string.
    /// </summary>
    public interface IJsonModel
    {
        /// <summary>
        /// Convert this object to a JObject.
        /// </summary>
        /// <returns>The new JObject.</returns>
        /// <param name="version">Version of the API to use.</param>
        JObject ToJObject(TCAPIVersion version);

		/// <summary>
		/// Convert this object to a JObject.
		/// </summary>
		/// <returns>The new JObject.</returns>
		JObject ToJObject();

        /// <summary>
        /// Convert this object to a JSON string.
        /// </summary>
        /// <returns>The new JSON string.</returns>
        /// <param name="version">Version of the API to use.</param>
        /// <param name="pretty">If set to <c>true</c>, string will be formatted with newlines and tabs.</param>
        string ToJSON(TCAPIVersion version, bool pretty = false);

		/// <summary>
		/// Convert this object to a JSON string.
		/// </summary>
		/// <returns>The new JSON string.</returns>
		/// <param name="pretty">If set to <c>true</c>, string will be formatted with newlines and tabs.</param>
		string ToJSON(bool pretty = false);
    }
}
<file_sep>/TinCan/ActivityDefinition.cs
/*
    Copyright 2014-2017 <NAME>

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
*/

using System;
using Newtonsoft.Json.Linq;
using TinCan.Json;

namespace TinCan
{
    /// <summary>
    /// Object defining information about an activity.
    /// </summary>
    public class ActivityDefinition : JsonModel
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="T:TinCan.ActivityDefinition"/> class.
        /// </summary>
        public ActivityDefinition() {}

        /// <summary>
        /// Initializes a new instance of the <see cref="T:TinCan.ActivityDefinition"/> class.
        /// </summary>
        /// <param name="json">String of JSON describing the object.</param>
        public ActivityDefinition(StringOfJSON json): this(json.ToJObject()) {}

        /// <summary>
        /// Initializes a new instance of the <see cref="T:TinCan.ActivityDefinition"/> class.
        /// </summary>
        /// <param name="jobj">JSON object describing the object.</param>
        public ActivityDefinition(JObject jobj)
        {
            if (jobj["type"] != null)
            {
                Type = new Uri(jobj.Value<string>("type"));
            }

            if (jobj["moreInfo"] != null)
            {
                MoreInfo = new Uri(jobj.Value<string>("moreInfo"));
            }

            if (jobj["name"] != null)
            {
                Name = (LanguageMap)jobj.Value<JObject>("name");
            }

            if (jobj["description"] != null)
            {
                Description = (LanguageMap)jobj.Value<JObject>("description");
            }

            if (jobj["extensions"] != null)
            {
                Extensions = (Extensions)jobj.Value<JObject>("extensions");
            }
        }

        /// <summary>
        /// Gets or sets the object type.
        /// </summary>
        /// <value>The type of the object.</value>
        public Uri Type { get; set; }

        /// <summary>
        /// Gets or sets the URI for accessing more info.
        /// </summary>
        /// <value>The more info endpoint.</value>
        public Uri MoreInfo { get; set; }

        /// <summary>
        /// Gets or sets the language map name of this activity.
        /// </summary>
        /// <value>The name.</value>
        public LanguageMap Name { get; set; }

        /// <summary>
        /// Gets or sets the description.
        /// </summary>
        /// <value>The description.</value>
        public LanguageMap Description { get; set; }

        /// <summary>
        /// Gets or sets the additional data associated with the activity.
        /// </summary>
        /// <value>The additional data.</value>
        public Extensions Extensions { get; set; }

        /// <inheritdoc />
        public override JObject ToJObject(TCAPIVersion version)
        {
            var result = new JObject();

            if (Type != null)
            {
                result.Add("type", Type.ToString());
            }

            if (MoreInfo != null)
            {
                result.Add("moreInfo", MoreInfo.ToString());
            }

            if (Name != null && !Name.IsEmpty)
            {
                result.Add("name", Name.ToJObject(version));
            }

            if (Description != null && !Description.IsEmpty)
            {
                result.Add("description", Description.ToJObject(version));
            }

            if (Extensions != null && ! Extensions.IsEmpty)
            {
                result.Add("extensions", Extensions.ToJObject(version));
            }

            return result;
        }

        /// <summary>
        /// Returns a <see cref="T:System.String"/> that represents the current <see cref="T:TinCan.ActivityDefinition"/>.
        /// </summary>
        /// <returns>A <see cref="T:System.String"/> that represents the current <see cref="T:TinCan.ActivityDefinition"/>.</returns>
        public override string ToString()
        {
            return string.Format("[ActivityDefinition: Type={0}, MoreInfo={1}, Name={2}, Description={3}, Extensions={4}]",
                                 Type, MoreInfo, Name, Description, Extensions);
        }

		/// <summary>
		/// Defines the operation to use when casting from a JObject to this type.
		/// </summary>
		/// <returns>The JObject as this type.</returns>
		/// <param name="jobj">The JObject to cast.</param>
		public static explicit operator ActivityDefinition(JObject jobj)
        {
            return new ActivityDefinition(jobj);
        }
    }
}
<file_sep>/TinCan/StatementsResult.cs
/*
    Copyright 2014-2017 <NAME>

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
*/

using System.Collections.Generic;
using Newtonsoft.Json.Linq;
using TinCan.Json;

namespace TinCan
{
    /// <summary>
    /// LRS result with statement information.
    /// </summary>
    public class StatementsResult
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="T:TinCan.StatementsResult"/> class.
        /// </summary>
        public StatementsResult() {}

        /// <summary>
        /// Initializes a new instance of the <see cref="T:TinCan.StatementsResult"/> class.
        /// </summary>
        /// <param name="str">String.</param>
        public StatementsResult(string str) : this(new StringOfJSON(str)) {}

        /// <summary>
        /// Initializes a new instance of the <see cref="T:TinCan.StatementsResult"/> class.
        /// </summary>
        /// <param name="json">Json.</param>
        public StatementsResult(StringOfJSON json) : this(json.ToJObject()) {}

        /// <summary>
        /// Initializes a new instance of the <see cref="T:TinCan.StatementsResult"/> class.
        /// </summary>
        /// <param name="statements">Statements.</param>
        public StatementsResult(List<Statement> statements)
        {
            Statements = statements;
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="T:TinCan.StatementsResult"/> class.
		/// </summary>
		/// <param name="jobj">Jobj.</param>
		public StatementsResult(JObject jobj)
		{
			if (jobj["statements"] != null)
			{
				Statements = new List<Statement>();

				foreach (var item in jobj.Value<JArray>("statements"))
				{
					Statements.Add(new Statement((JObject)item));
				}
			}

			if (jobj["more"] != null)
			{
				More = jobj.Value<string>("more");
			}
		}

		/// <summary>
		/// Gets or sets the statements from the LRS.
		/// </summary>
		/// <value>The statements.</value>
		public List<Statement> Statements { get; set; }

		/// <summary>
		/// Gets or sets the ID to use to get more results, if applicable.
		/// </summary>
		/// <value>The identifier.</value>
		public string More { get; set; }

        /// <summary>
        /// Returns a <see cref="T:System.String"/> that represents the current <see cref="T:TinCan.StatementsResult"/>.
        /// </summary>
        /// <returns>A <see cref="T:System.String"/> that represents the current <see cref="T:TinCan.StatementsResult"/>.</returns>
        public override string ToString()
        {
            return string.Format("[StatementsResult: Statements={0}, More={1}]",
                                 Statements, More);
        }
    }
}
<file_sep>/TinCan/json/StringOfJSON.cs
/*
    Copyright 2014-2017 <NAME>

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
*/

using System;
using Newtonsoft.Json.Linq;

namespace TinCan.Json
{
    /// <summary>
    /// An object that stores a string that can be parsed to a JObject.
    /// </summary>
    public class StringOfJSON : JsonModel
    {
        readonly string source;

        /// <summary>
        /// Initializes a new instance of the <see cref="T:TinCan.Json.StringOfJSON"/> class.
        /// </summary>
        /// <param name="json">String JSON content.</param>
        public StringOfJSON(string json)
        {
            if (string.IsNullOrWhiteSpace(json))
            {
                throw new ArgumentException(nameof(json));
            }

            source = json;
        }

        /// <summary>
        /// Parse this string to a new JObject.
        /// </summary>
        /// <returns>The string as a JObject.</returns>
        /// <param name="version">Version of the API to use; currently unused.</param>
        public override JObject ToJObject(TCAPIVersion version)
        {
            return JObject.Parse(source);
        }

        /// <summary>
        /// Returns a <see cref="T:System.String"/> that represents the current <see cref="T:TinCan.Json.StringOfJSON"/>.
        /// </summary>
        /// <returns>A <see cref="T:System.String"/> that represents the current <see cref="T:TinCan.Json.StringOfJSON"/>.</returns>
        public override string ToString()
        {
            return source;
        }
    }
}
<file_sep>/TinCan/Group.cs
/*
    Copyright 2014-2017 <NAME>

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
*/

using System.Collections.Generic;
using Newtonsoft.Json.Linq;
using TinCan.Json;

namespace TinCan
{
	/// <summary>
	/// A Group represents a collection of Agents and can be used in most of the same situations an Agent can be used.
	/// </summary>
	public class Group : Agent
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="T:TinCan.Group"/> class.
        /// </summary>
        public Group() { }

        /// <summary>
        /// Initializes a new instance of the <see cref="T:TinCan.Group"/> class.
        /// </summary>
        /// <param name="json">Json.</param>
        public Group(StringOfJSON json) : this(json.ToJObject()) { }

        /// <summary>
        /// Initializes a new instance of the <see cref="T:TinCan.Group"/> class.
        /// </summary>
        /// <param name="jobj">Jobj.</param>
        public Group(JObject jobj) : base(jobj)
        {
            if (jobj["member"] != null)
            {
                Members = new List<Agent>();

                foreach (JObject jagent in jobj["member"])
                {
                    Members.Add(new Agent(jagent));
                }
            }
        }

        /// <summary>
        /// Gets the type of the object.
        /// </summary>
        /// <value>The type of the object.</value>
        public override string ObjectType
        {
            get
            {
                return TypeName;
            }
        }

        /// <summary>
        /// Gets the members of this group.
        /// </summary>
        /// <value>The member.</value>
        public List<Agent> Members { get; }

        /// <inheritdoc />
        public override JObject ToJObject(TCAPIVersion version)
        {
            var result = base.ToJObject(version);

            if (Members != null && Members.Count > 0)
            {
                var jmember = new JArray();
                result.Add("member", jmember);

                foreach (var agent in Members)
                {
                    jmember.Add(agent.ToJObject(version));
                }
            }

            return result;
        }

        /// <summary>
        /// Returns a <see cref="T:System.String"/> that represents the current <see cref="T:TinCan.Group"/>.
        /// </summary>
        /// <returns>A <see cref="T:System.String"/> that represents the current <see cref="T:TinCan.Group"/>.</returns>
        public override string ToString()
        {
            return string.Format("[Agent: Name={0}, Mbox={1}, MboxSha1Sum={2}, OpenId={3}, Account={4}, Members={5}]",
								 Name, Mbox, MboxSha1Sum, OpenId, Account, Members);
        }

        /// <summary>
        /// The type of the object.
        /// </summary>
        public static readonly new string TypeName = nameof(Group);

		/// <summary>
		/// Defines the operation to use when casting from a JObject to this type.
		/// </summary>
		/// <returns>The JObject as this type.</returns>
		/// <param name="jobj">The JObject to cast.</param>
		public static explicit operator Group(JObject jobj)
        {
            return new Group(jobj);
        }
    }
}
<file_sep>/TinCan/StatementsQuery.cs
/*
    Copyright 2014-2017 <NAME>

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
*/

using System;
using System.Collections.Generic;

namespace TinCan
{
    /// <summary>
    /// An object describing a query for statements.
    /// </summary>
    public class StatementsQuery
    {
        string activityId;

        /// <summary>
        /// Gets or sets the agent to query.
        /// </summary>
        /// <value>The agent.</value>
        public Agent Agent { get; set; }

        /// <summary>
        /// Gets or sets the verb identifier to query.
        /// </summary>
        /// <value>The verb identifier.</value>
        public Uri VerbId { get; set; }

        /// <summary>
        /// Gets or sets the activity identifier to query.
        /// </summary>
        /// <value>The activity identifier.</value>
        public string ActivityId
        {
            get
            {
                return activityId;
            }

            set
            {
                var uri = new Uri(value);
                activityId = value;
            }
        }

        /// <summary>
        /// Gets or sets the registration ID to query.
        /// </summary>
        /// <value>The registration.</value>
        public Guid? Registration { get; set; }

		/// <summary>
		/// Gets or sets whether or not to apply the activity filter broadly.
		/// </summary>
		/// <value>Whether or not to apply the activity filter broadly.</value>
		public bool? RelatedActivities { get; set; }

		/// <summary>
		/// Gets or sets whether or not to apply the agent filter broadly.
		/// </summary>
		/// <value>Whether or not to apply the agent filter broadly.</value>
		public bool? RelatedAgents { get; set; }

		/// <summary>
		/// Gets or sets the time for which only Statements stored since the specified Timestamp (exclusive) are returned.
		/// </summary>
		/// <value>The timestamp.</value>
		public DateTime? Since { get; set; }

		/// <summary>
		/// Gets or sets the time for which only Statements stored at or before the specified Timestamp are returned.
		/// </summary>
		/// <value>The timestamp.</value>
		public DateTime? Until { get; set; }

        /// <summary>
        /// Gets or sets the maximum number of statements to return.
        /// </summary>
        /// <value>The limit.</value>
        public Int32? Limit { get; set; }

        /// <summary>
        /// Gets or sets the format to return.
        /// </summary>
        /// <value>The format.</value>
        public StatementsQueryResultFormat Format { get; set; }

		/// <summary>
		/// Gets or sets whether or not to return in ascending order.
		/// </summary>
		/// <value>Whether or not to return in ascending order.</value>
		public bool? Ascending { get; set; }

        /// <summary>
        /// Convert this query to a query parameter map.
        /// </summary>
        /// <returns>The parameter map.</returns>
        /// <param name="version">Version of xAPI to use when building the map.</param>
        public Dictionary<string, string> ToParameterMap (TCAPIVersion version)
        {
            var result = new Dictionary<string, string>();

            if (Agent != null)
            {
                result.Add("agent", Agent.ToJSON(version));
            }

            if (VerbId != null)
            {
                result.Add("verb", VerbId.ToString());
            }

            if (ActivityId != null)
            {
                result.Add("activity", ActivityId);
            }

            if (Registration != null)
            {
                result.Add("registration", Registration.Value.ToString());
            }

            if (RelatedActivities != null)
            {
                result.Add("related_activities", RelatedActivities.Value.ToString());
            }

            if (RelatedAgents != null)
            {
                result.Add("related_agents", RelatedAgents.Value.ToString());
            }

            if (Since != null)
            {
                result.Add("since", Since.Value.ToString(TimeFormat.Default));
            }

            if (Until != null)
            {
                result.Add("until", Until.Value.ToString(TimeFormat.Default));
            }

            if (Limit != null)
            {
                result.Add("limit", Limit.ToString());
            }

            if (Format != null)
            {
                result.Add("format", Format.ToString());
            }

            if (Ascending != null)
            {
                result.Add("ascending", Ascending.Value.ToString());
            }

            return result;
        }

        /// <summary>
        /// Returns a <see cref="T:System.String"/> that represents the current <see cref="T:TinCan.StatementsQuery"/>.
        /// </summary>
        /// <returns>A <see cref="T:System.String"/> that represents the current <see cref="T:TinCan.StatementsQuery"/>.</returns>
        public override string ToString()
        {
            return string.Format("[StatementsQuery: Agent={0}, VerbId={1}, ActivityId={2}, Registration={3}, RelatedActivities={4}, RelatedAgents={5}, Since={6}, Until={7}, Limit={8}, Format={9}, Ascending={10}]",
                                 Agent, VerbId, ActivityId, Registration, RelatedActivities, RelatedAgents, Since, Until, Limit, Format, Ascending);
        }
    }
}
<file_sep>/TinCanTests/RemoteLRSResourceTest.cs
/*
    Copyright 2014-2017 <NAME>

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
*/

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using NUnit.Framework;
using TinCan;
using TinCan.Documents;
using TinCan.LRSResponses;

namespace TinCanTests
{
    [TestFixture]
    class RemoteLRSResourceTest
    {
        RemoteLRS lrs;

        [SetUp]
        public void Init()
        {
            // these are credentials used by the other OSS libs when building via Travis-CI
            // so are okay to include in the repository, if you wish to have access to the
            // results of the test suite then supply your own endpoint, username, and password
            lrs = new RemoteLRS(
                new Uri("https://cloud.scorm.com/tc/U2S4SI5FY0/sandbox/"),
                "Nja986GYE1_XrWMmFUE",
                "Bd9lDr1kjaWWY6RID_4"
            );
        }

        [Test]
        public async Task TestAbout()
        {
            var lrsRes = await lrs.About();
            Assert.IsTrue(lrsRes.Success);
        }

        [Test]
        public async Task TestAboutFailure()
        {
			var invalidAboutLRS = new RemoteLRS(
                new Uri("http://cloud.scorm.com/tc/3TQLAI9/sandbox/"),
				"Nja986GYE1_XrWMmFUE",
				"Bd9lDr1kjaWWY6RID_4"
			);

            var lrsRes = await invalidAboutLRS.About();
            Assert.IsFalse(lrsRes.Success);
        }

        [Test]
        public async Task TestSaveStatement()
        {
            var statement = new Statement
            {
                Actor = Support.Agent,
                Verb = Support.Verb,
                Target = Support.Activity
            };

            var lrsRes = await lrs.SaveStatement(statement);
            Assert.IsTrue(lrsRes.Success);
            Assert.AreEqual(statement, lrsRes.Content);
            Assert.IsNotNull(lrsRes.Content.Id);
        }

        [Test]
        public async Task TestSaveStatementWithID()
        {
            var statement = new Statement();
            statement.Stamp();
            statement.Actor = Support.Agent;
            statement.Verb = Support.Verb;
            statement.Target = Support.Activity;

            var lrsRes = await lrs.SaveStatement(statement);
            Assert.IsTrue(lrsRes.Success);
            Assert.AreEqual(statement, lrsRes.Content);
        }

        [Test]
        public async Task TestSaveStatementStatementRef()
        {
            var statement = new Statement();
            statement.Stamp();
            statement.Actor = Support.Agent;
            statement.Verb = Support.Verb;
            statement.Target = Support.StatementRef;

            var lrsRes = await lrs.SaveStatement(statement);
            Assert.IsTrue(lrsRes.Success);
            Assert.AreEqual(statement, lrsRes.Content);
        }

        [Test]
        public async Task TestSaveStatementSubStatement()
        {
            var statement = new Statement();
            statement.Stamp();
            statement.Actor = Support.Agent;
            statement.Verb = Support.Verb;
            statement.Target = Support.SubStatement;

            var lrsRes = await lrs.SaveStatement(statement);
            Assert.IsTrue(lrsRes.Success);
            Assert.AreEqual(statement, lrsRes.Content);
        }

        [Test]
        public async Task TestVoidStatement()
        {
            var toVoid = Guid.NewGuid();
            var lrsRes = await lrs.VoidStatement(toVoid, Support.Agent);

            Assert.IsTrue(lrsRes.Success, "LRS response successful");
            Assert.AreEqual(new Uri("http://adlnet.gov/expapi/verbs/voided"), lrsRes.Content.Verb.Id, "voiding statement uses voided verb");
            Assert.AreEqual(toVoid, ((StatementRef) lrsRes.Content.Target).Id, "voiding statement target correct id");
        }

        [Test]
        public async Task TestSaveStatements()
        {
            var statement1 = new Statement
            {
                Actor = Support.Agent,
                Verb = Support.Verb,
                Target = Support.Parent
            };

            var statement2 = new Statement
            {
                Actor = Support.Agent,
                Verb = Support.Verb,
                Target = Support.Activity,
                Context = Support.Context
            };

            var statements = new List<Statement>
            {
                statement1,
                statement2
            };

            var lrsRes = await lrs.SaveStatements(statements);
            Assert.IsTrue(lrsRes.Success);
            // TODO: check statements match and ids not null
        }

        [Test]
        public async Task TestRetrieveStatement()
        {
            var statement = new Statement();
            statement.Stamp();
            statement.Actor = Support.Agent;
            statement.Verb = Support.Verb;
            statement.Target = Support.Activity;
            statement.Context = Support.Context;
            statement.Result = Support.Result;

            var saveRes = await lrs.SaveStatement(statement);

            if (saveRes.Success)
            {
                var retRes = await lrs.RetrieveStatement(saveRes.Content.Id.Value);
                Assert.IsTrue(retRes.Success);
            }
            else
            {
                // TODO: skipped?
            }
        }

        [Test]
        public async Task TestQueryStatements()
        {
            var query = new StatementsQuery
            {
                Agent = Support.Agent,
                VerbId = Support.Verb.Id,
                ActivityId = Support.Parent.Id,
                RelatedActivities = true,
                RelatedAgents = true,
                Format = StatementsQueryResultFormat.Ids,
                Limit = 10
            };

            var lrsRes = await lrs.QueryStatements(query);
            Assert.IsTrue(lrsRes.Success);
        }

        [Test]
        public async Task TestMoreStatements()
        {
            var query = new StatementsQuery
            {
                Format = StatementsQueryResultFormat.Ids,
                Limit = 2
            };

            var queryRes = await lrs.QueryStatements(query);

            if (queryRes.Success && queryRes.Content.More != null)
            {
                var moreRes = await lrs.MoreStatements(queryRes.Content);
                Assert.IsTrue(moreRes.Success);
            }
            else
            {
                // TODO: skipped?
            }
        }

        [Test]
        public async Task TestRetrieveStateIds()
        {
            var lrsRes = await lrs.RetrieveStateIds(Support.Activity, Support.Agent);
            Assert.IsTrue(lrsRes.Success);
        }

        [Test]
        public async Task TestRetrieveState()
        {
            var lrsRes = await lrs.RetrieveState("test", Support.Activity, Support.Agent);
            Assert.IsTrue(lrsRes.Success);
            Assert.IsInstanceOf<StateDocument>(lrsRes.Content);
        }

        [Test]
        public async Task TestSaveState()
        {
            var doc = new StateDocument
            {
                Activity = Support.Activity,
                Agent = Support.Agent,
                Id = "test",
                Content = Encoding.UTF8.GetBytes("Test value")
            };

            var lrsRes = await lrs.SaveState(doc);
            Assert.IsTrue(lrsRes.Success);
        }

        [Test]
        public async Task TestDeleteState()
        {
            var doc = new StateDocument
            {
                Activity = Support.Activity,
                Agent = Support.Agent,
                Id = "test"
            };

            var lrsRes = await lrs.DeleteState(doc);
            Assert.IsTrue(lrsRes.Success);
        }

        [Test]
        public async Task TestClearState()
        {
            var lrsRes = await lrs.ClearState(Support.Activity, Support.Agent);
            Assert.IsTrue(lrsRes.Success);
        }

        [Test]
        public async Task TestRetrieveActivityProfileIds()
        {
            var lrsRes = await lrs.RetrieveActivityProfileIds(Support.Activity);
            Assert.IsTrue(lrsRes.Success);
        }

        [Test]
        public async Task TestRetrieveActivityProfile()
        {
            var lrsRes = await lrs.RetrieveActivityProfile("test", Support.Activity);
            Assert.IsTrue(lrsRes.Success);
            Assert.IsInstanceOf<ActivityProfileDocument>(lrsRes.Content);
        }

        [Test]
        public async Task TestSaveActivityProfile()
        {
            var doc = new ActivityProfileDocument
            {
                Activity = Support.Activity,
                Id = "test",
                Content = Encoding.UTF8.GetBytes("Test value")
            };

            var lrsRes = await lrs.SaveActivityProfile(doc);
            Assert.IsTrue(lrsRes.Success);
        }

        [Test]
        public async Task TestDeleteActivityProfile()
        {
            var doc = new ActivityProfileDocument
            {
                Activity = Support.Activity,
                Id = "test"
            };

            var lrsRes = await lrs.DeleteActivityProfile(doc);
            Assert.IsTrue(lrsRes.Success);
        }

        [Test]
        public async Task TestRetrieveAgentProfileIds()
        {
            var lrsRes = await lrs.RetrieveAgentProfileIds(Support.Agent);
            Assert.IsTrue(lrsRes.Success);
        }

        [Test]
        public async Task TestRetrieveAgentProfile()
        {
            var lrsRes = await lrs.RetrieveAgentProfile("test", Support.Agent);
            Assert.IsTrue(lrsRes.Success);
            Assert.IsInstanceOf<AgentProfileDocument>(lrsRes.Content);
        }

        [Test]
        public async Task TestSaveAgentProfile()
        {
            var doc = new AgentProfileDocument
            {
                Agent = Support.Agent,
                Id = "test",
                Content = Encoding.UTF8.GetBytes("Test value")
            };

            var lrsRes = await lrs.SaveAgentProfile(doc);
            Assert.IsTrue(lrsRes.Success);
        }

        [Test]
        public async Task TestDeleteAgentProfile()
        {
            var doc = new AgentProfileDocument
            {
                Agent = Support.Agent,
                Id = "test"
            };

            var lrsRes = await lrs.DeleteAgentProfile(doc);
            Assert.IsTrue(lrsRes.Success);
        }
    }
}
<file_sep>/TinCan/StatementBase.cs
/*
    Copyright 2014-2017 <NAME>

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
*/

using System;
using Newtonsoft.Json.Linq;
using TinCan.Json;

namespace TinCan
{
    /// <summary>
    /// The abstract base for statement objects.
    /// </summary>
    public abstract class StatementBase : JsonModel
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="T:TinCan.StatementBase"/> class.
        /// </summary>
        internal StatementBase() { }

        /// <summary>
        /// Initializes a new instance of the <see cref="T:TinCan.StatementBase"/> class.
        /// </summary>
        /// <param name="json">Json.</param>
        internal StatementBase(StringOfJSON json) : this(json.ToJObject()) { }

        /// <summary>
        /// Initializes a new instance of the <see cref="T:TinCan.StatementBase"/> class.
        /// </summary>
        /// <param name="jobj">Jobj.</param>
        internal StatementBase(JObject jobj)
        {
            if (jobj["actor"] != null)
            {
                if (jobj["actor"]["objectType"] != null && (string)jobj["actor"]["objectType"] == Group.TypeName)
                {
                    Actor = (Group)jobj.Value<JObject>("actor");
                }
                else
                {
                    Actor = (Agent)jobj.Value<JObject>("actor");
                }
            }

            if (jobj["verb"] != null)
            {
                Verb = (Verb)jobj.Value<JObject>("verb");
            }

            if (jobj["object"] != null)
            {
                if (jobj["object"]["objectType"] != null)
                {
                    if ((string)jobj["object"]["objectType"] == Group.TypeName)
                    {
                        Target = (Group)jobj.Value<JObject>("object");
                    }
                    else if ((string)jobj["object"]["objectType"] == Agent.TypeName)
                    {
                        Target = (Agent)jobj.Value<JObject>("object");
                    }
                    else if ((string)jobj["object"]["objectType"] == Activity.TypeName)
                    {
                        Target = (Activity)jobj.Value<JObject>("object");
                    }
                    else if ((string)jobj["object"]["objectType"] == StatementRef.TypeName)
                    {
                        Target = (StatementRef)jobj.Value<JObject>("object");
                    }
                }
                else
                {
                    Target = (Activity)jobj.Value<JObject>("object");
                }
            }

            if (jobj["result"] != null)
            {
                Result = (Result)jobj.Value<JObject>("result");
            }

            if (jobj["context"] != null)
            {
                Context = (Context)jobj.Value<JObject>("context");
            }

            if (jobj["timestamp"] != null)
            {
                Timestamp = jobj.Value<DateTime>("timestamp");
            }
        }

		/// <summary>
		/// Gets or sets the actor related to this statement.
		/// </summary>
		/// <value>The actor.</value>
		public Agent Actor { get; set; }

		/// <summary>
		/// Gets or sets the verb related to this statement.
		/// </summary>
		/// <value>The verb.</value>
		public Verb Verb { get; set; }

		/// <summary>
		/// Gets or sets the target related to this statement.
		/// </summary>
		/// <value>The target.</value>
		public IStatementTarget Target { get; set; }

		/// <summary>
		/// Gets or sets the result related to this statement.
		/// </summary>
		/// <value>The result.</value>
		public Result Result { get; set; }

		/// <summary>
		/// Gets or sets the context of this statement.
		/// </summary>
		/// <value>The context.</value>
		public Context Context { get; set; }

		/// <summary>
		/// Gets or sets the timestamp of this statement.
		/// </summary>
		/// <value>The timestamp.</value>
		public DateTime? Timestamp { get; set; }

        /// <inheritdoc />
        public override JObject ToJObject(TCAPIVersion version)
        {
            var result = new JObject();

            if (Actor != null)
            {
                result.Add("actor", Actor.ToJObject(version));
            }

            if (Verb != null)
            {
                result.Add("verb", Verb.ToJObject(version));
            }

            if (Target != null)
            {
                result.Add("object", Target.ToJObject(version));
            }

            if (Result != null)
            {
                result.Add("result", this.Result.ToJObject(version));
            }

            if (Context != null)
            {
                result.Add("context", Context.ToJObject(version));
            }

            if (Timestamp != null)
            {
                result.Add("timestamp", Timestamp.Value.ToString(TimeFormat.Default));
            }

            return result;
        }
    }
}
<file_sep>/TinCan/Score.cs
/*
    Copyright 2014-2017 <NAME>

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
*/

using System;
using Newtonsoft.Json.Linq;
using TinCan.Json;

namespace TinCan
{
	/// <summary>
	/// An optional property that represents the outcome of a graded Activity achieved by an Agent.
	/// </summary>
	public class Score : JsonModel
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="T:TinCan.Score"/> class.
        /// </summary>
        public Score() {}

        /// <summary>
        /// Initializes a new instance of the <see cref="T:TinCan.Score"/> class.
        /// </summary>
        /// <param name="json">Json.</param>
        public Score(StringOfJSON json): this(json.ToJObject()) {}

        /// <summary>
        /// Initializes a new instance of the <see cref="T:TinCan.Score"/> class.
        /// </summary>
        /// <param name="jobj">Jobj.</param>
        public Score(JObject jobj)
        {
            if (jobj["scaled"] != null)
            {
                Scaled = jobj.Value<Double>("scaled");
            }

            if (jobj["raw"] != null)
            {
                Raw = jobj.Value<Double>("raw");
            }

            if (jobj["min"] != null)
            {
                Min = jobj.Value<Double>("min");
            }

            if (jobj["max"] != null)
            {
                Max = jobj.Value<Double>("max");
            }
        }

		/// <summary>
		/// Gets or sets the score related to the experience as modified by scaling and/or normalization.
		/// </summary>
		/// <value>The score related to the experience as modified by scaling and/or normalization.</value>
		public Double? Scaled { get; set; }

		/// <summary>
		/// Gets or sets the score achieved by the Actor in the experience described by the Statement.
		/// This is not modified by any scaling or normalization.
		/// </summary>
		/// <value>The score achieved by the Actor in the experience described by the Statement.</value>
		public Double? Raw { get; set; }

		/// <summary>
		/// Gets or sets the lowest possible score for the experience described by the Statement.
		/// </summary>
		/// <value>The lowest possible score for the experience described by the Statement.</value>
		public Double? Min { get; set; }

		/// <summary>
		/// Gets or sets the highest possible score for the experience described by the Statement.
		/// </summary>
		/// <value>The highest possible score for the experience described by the Statement.</value>
		public Double? Max { get; set; }

        /// <inheritdoc />
        public override JObject ToJObject(TCAPIVersion version)
        {
            var result = new JObject();

            if (Scaled != null)
            {
                result.Add("scaled", Scaled);
            }

            if (Raw != null)
            {
                result.Add("raw", Raw);
            }

            if (Min != null)
            {
                result.Add("min", Min);
            }

            if (Max != null)
            {
                result.Add("max", Max);
            }

            return result;
        }

        /// <summary>
        /// Returns a <see cref="T:System.String"/> that represents the current <see cref="T:TinCan.Score"/>.
        /// </summary>
        /// <returns>A <see cref="T:System.String"/> that represents the current <see cref="T:TinCan.Score"/>.</returns>
        public override string ToString()
        {
            return string.Format("[Score: Scaled={0}, Raw={1}, Min={2}, Max={3}]",
                                 Scaled, Raw, Min, Max);
        }

		/// <summary>
		/// Defines the operation to use when casting from a JObject to this type.
		/// </summary>
		/// <returns>The JObject as this type.</returns>
		/// <param name="jobj">The JObject to cast.</param>
		public static explicit operator Score(JObject jobj)
        {
            return new Score(jobj);
        }
    }
}
<file_sep>/TinCan/About.cs
/*
    Copyright 2014-2017 <NAME>

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
*/

using System.Collections.Generic;
using Newtonsoft.Json.Linq;
using TinCan.Json;

namespace TinCan
{
    /// <summary>
    /// An object containing information about this LRS.
    /// </summary>
    public sealed class About : JsonModel
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="T:TinCan.About"/> class.
        /// </summary>
        /// <param name="str">String of JSON content for an About resource.</param>
        public About(string str) : this(new StringOfJSON(str)) {}

		/// <summary>
		/// Initializes a new instance of the <see cref="T:TinCan.About"/> class.
		/// </summary>
		/// <param name="json">String of JSON content for an About resource.</param>
		public About(StringOfJSON json) : this(json.ToJObject()) {}

        /// <summary>
        /// Initializes a new instance of the <see cref="T:TinCan.About"/> class.
        /// </summary>
        /// <param name="jobj">JSON content for an About resource.</param>
        public About(JObject jobj)
        {
            if (jobj["version"] != null)
            {
                Versions = new List<TCAPIVersion>();

                foreach (string item in jobj.Value<JArray>("version"))
                {
                    Versions.Add((TCAPIVersion)item);
                }
            }

            if (jobj["extensions"] != null)
            {
                Extensions = new Extensions(jobj.Value<JObject>("extensions"));
            }
        }

        /// <summary>
        /// Gets the xAPI versions this LRS supports.
        /// </summary>
        /// <value>The supported versions.</value>
        public List<TCAPIVersion> Versions { get; }

        /// <summary>
        /// Gets the map of other properties as needed.
        /// </summary>
        /// <value>Other needed properties.</value>
        public Extensions Extensions { get; }

        /// <inheritdoc />
        public override JObject ToJObject(TCAPIVersion version) {
            JObject result = new JObject();

            if (Versions != null)
            {
                var versions = new JArray();

                foreach (var ver in Versions)
                {
                    versions.Add(ver.ToString());
                }

                result.Add("version", versions);
            }

            if (Extensions != null && !Extensions.IsEmpty)
            {
                result.Add("extensions", Extensions.ToJObject(version));
            }

            return result;
        }

        /// <summary>
        /// Returns a <see cref="T:System.String"/> that represents the current <see cref="T:TinCan.About"/>.
        /// </summary>
        /// <returns>A <see cref="T:System.String"/> that represents the current <see cref="T:TinCan.About"/>.</returns>
        public override string ToString()
        {
            return string.Format("[About: Versions={0}, Extensions={1}]",
                                 Versions, Extensions);
        }

		/// <summary>
		/// Defines the operation to use when casting from a JObject to this type.
		/// </summary>
		/// <returns>The JObject as this type.</returns>
		/// <param name="jobj">The JObject to cast.</param>
		public static explicit operator About(JObject jobj)
        {
            return new About(jobj);
        }
    }
}
<file_sep>/TinCan/RemoteLRS.cs
/*
    Copyright 2014-2017 <NAME>

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
*/

using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using TinCan.Documents;
using TinCan.LRSResponses;

namespace TinCan
{
    /// <summary>
    /// Remote lrs.
    /// </summary>
    public class RemoteLRS : ILRS
    {
        readonly HttpClient client = new HttpClient();

        /// <summary>
        /// Initializes a new instance of the <see cref="T:TinCan.RemoteLRS"/> class.
        /// </summary>
        /// <param name="endpoint">Endpoint of the remote LRS.</param>
        public RemoteLRS(Uri endpoint)
        {
            Endpoint = endpoint ?? throw new ArgumentNullException(nameof(endpoint));
            Version = TCAPIVersion.Latest;
        }

		/// <summary>
		/// Initializes a new instance of the <see cref="T:TinCan.RemoteLRS"/> class.
		/// </summary>
		/// <param name="endpoint">Endpoint of the remote LRS.</param>
		/// <param name="version">Version of xAPI to use.</param>
		public RemoteLRS(Uri endpoint, TCAPIVersion version)
        {
			Endpoint = endpoint ?? throw new ArgumentNullException(nameof(endpoint));
            Version = version ?? throw new ArgumentNullException(nameof(version));
        }

		/// <summary>
		/// Initializes a new instance of the <see cref="T:TinCan.RemoteLRS"/> class.
		/// </summary>
		/// <param name="endpoint">Endpoint of the remote LRS.</param>
		/// <param name="username">Username to use when authenticating with the LRS.</param>
		/// <param name="password">Password to use when authenticating with the LRS.</param>
		public RemoteLRS(Uri endpoint, string username, string password) : this(endpoint)
		{
			SetAuth(username, password);
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="T:TinCan.RemoteLRS"/> class.
		/// </summary>
		/// <param name="endpoint">Endpoint of the remote LRS.</param>
		/// <param name="version">Version of xAPI to use.</param>
		/// <param name="username">Username to use when authenticating with the LRS.</param>
		/// <param name="password">Password to use when authenticating with the LRS.</param>
		public RemoteLRS(Uri endpoint, TCAPIVersion version, string username, string password) : this(endpoint, version)
        {
            SetAuth(username, password);
        }

        /// <summary>
        /// Gets the remote LRS endpoint.
        /// </summary>
        /// <value>The endpoint of the LRS.</value>
        public Uri Endpoint { get; }

        /// <summary>
        /// Gets the xAPI version.
        /// </summary>
        /// <value>The xAPI version.</value>
        public TCAPIVersion Version { get; }

        /// <summary>
        /// Gets or sets the authentication header.
        /// </summary>
        /// <value>The authentication header.</value>
        public string Auth { get; private set; }

        /// <summary>
        /// Gets the extended parameters.
        /// </summary>
        /// <value>The extended parameters.</value>
        public Dictionary<string, string> Extended { get; } = new Dictionary<string, string>();

        /// <summary>
        /// Sets basic authentication with a username and password.
        /// </summary>
        /// <param name="username">Username for authentication.</param>
        /// <param name="password">Password for authentication.</param>
        public void SetAuth(string username, string password)
        {
            Auth = string.Format("Basic {0}", Convert.ToBase64String(Encoding.UTF8.GetBytes(string.Format("{0}:{1}", username, password))));
        }

        /// <inheritdoc />
        public async Task<AboutLRSResponse> About()
        {
            var response = new AboutLRSResponse();

            var request = new LRSHttpRequest
            {
                Method = HttpMethod.Get,
                Resource = "about"
            };

            var res = await MakeRequest(request);

            if (res.Status != HttpStatusCode.OK)
            {
                response.Success = false;
                response.HttpException = res.Exception;
                response.SetErrMsgFromBytes(res.Content);
                return response;
            }

            response.Success = true;
            response.Content = new About(Encoding.UTF8.GetString(res.Content));

            return response;
        }

        /// <inheritdoc />
        public async Task<StatementLRSResponse> SaveStatement(Statement statement)
        {
            if (statement == null)
            {
                throw new ArgumentNullException(nameof(statement));
            }

            var response = new StatementLRSResponse();

            var request = new LRSHttpRequest()
            {
                QueryParams = new Dictionary<string, string>(),
                Resource = "statements"
            };

            if (statement.Id == null)
            {
                request.Method = HttpMethod.Post;
            }
            else
            {
                request.Method = HttpMethod.Put;
                request.QueryParams.Add("statementId", statement.Id.ToString());
            }

            request.ContentType = "application/json";
            request.Content = Encoding.UTF8.GetBytes(statement.ToJSON(Version));

            var res = await MakeRequest(request);

            if (statement.Id == null)
            {
                if (res.Status != HttpStatusCode.OK)
                {
                    response.Success = false;
                    response.HttpException = res.Exception;
                    response.SetErrMsgFromBytes(res.Content);
                    return response;
                }

                var ids = JArray.Parse(Encoding.UTF8.GetString(res.Content));
                statement.Id = new Guid((string)ids[0]);
            }
            else
            {
                if (res.Status != HttpStatusCode.NoContent)
                {
                    response.Success = false;
                    response.HttpException = res.Exception;
                    response.SetErrMsgFromBytes(res.Content);
                    return response;
                }
            }

            response.Success = true;
            response.Content = statement;

            return response;
        }

        /// <inheritdoc />
        public async Task<StatementLRSResponse> VoidStatement(Guid id, Agent agent)
        {
            if (agent == null)
            {
                throw new ArgumentNullException(nameof(agent));
            }

            var voidStatement = new Statement
            {
                Actor = agent,
                Verb = new Verb
                {
                    Id = new Uri("http://adlnet.gov/expapi/verbs/voided"),
                    Display = new LanguageMap()
                },
                Target = new StatementRef
                {
                    Id = id
                }
            };

            voidStatement.Verb.Display.Add("en-US", "voided");

            return await SaveStatement(voidStatement);
        }

        /// <inheritdoc />
        public async Task<StatementsResultLRSResponse> SaveStatements(List<Statement> statements)
        {
            if (statements == null)
            {
                throw new ArgumentNullException(nameof(statements));
            }

            if (statements.Count < 1)
            {
                throw new ArgumentException("No statements given to SaveStatements");
            }

            var response = new StatementsResultLRSResponse();

            var request = new LRSHttpRequest
            {
                Method = HttpMethod.Post,
                Resource = "statements",
                ContentType = "application/json"
            };

            var jarray = new JArray();

            foreach (var statement in statements)
            {
                jarray.Add(statement.ToJObject(Version));
            }

            request.Content = Encoding.UTF8.GetBytes(jarray.ToString());

            var res = await MakeRequest(request);

            if (res.Status != HttpStatusCode.OK)
            {
                response.Success = false;
                response.HttpException = res.Exception;
                response.SetErrMsgFromBytes(res.Content);
                return response;
            }

            var ids = JArray.Parse(Encoding.UTF8.GetString(res.Content));

            for (int i = 0; i < ids.Count; i++)
            {
                statements[i].Id = new Guid((string)ids[i]);
            }

            response.Success = true;
            response.Content = new StatementsResult(statements);

            return response;
        }

		/// <inheritdoc />
		public async Task<StatementLRSResponse> RetrieveStatement(Guid id)
        {
            return await GetStatement(new Dictionary<string, string>
            {
                { "statementId", id.ToString() }
            });
        }

        /// <inheritdoc />
        public async Task<StatementLRSResponse> RetrieveVoidedStatement(Guid id)
        {
            return await GetStatement(new Dictionary<string, string>
            {
                { "voidedStatementId", id.ToString() }
            });
        }

        /// <inheritdoc />
        public async Task<StatementsResultLRSResponse> QueryStatements(StatementsQuery query)
        {
            if (query == null)
            {
                throw new ArgumentNullException(nameof(query));
            }

            var response = new StatementsResultLRSResponse();

            var request = new LRSHttpRequest
            {
                Method = HttpMethod.Get,
                Resource = "statements",
                QueryParams = query.ToParameterMap(Version)
            };

            var res = await MakeRequest(request);

            if (res.Status != HttpStatusCode.OK)
            {
                response.Success = false;
                response.HttpException = res.Exception;
                response.SetErrMsgFromBytes(res.Content);
                return response;
            }

            response.Success = true;
            response.Content = new StatementsResult(new Json.StringOfJSON(Encoding.UTF8.GetString(res.Content)));

            return response;
        }

        /// <inheritdoc />
        public async Task<StatementsResultLRSResponse> MoreStatements(StatementsResult result)
        {
            var response = new StatementsResultLRSResponse();

            var request = new LRSHttpRequest()
            {
                Method = HttpMethod.Get,
                Resource = Endpoint.GetLeftPart(UriPartial.Authority)
            };

            if (!request.Resource.EndsWith("/", StringComparison.Ordinal))
            {
                request.Resource += "/";
            }

            request.Resource += result.More;

            var res = await MakeRequest(request);

            if (res.Status != HttpStatusCode.OK)
            {
                response.Success = false;
                response.HttpException = res.Exception;
                response.SetErrMsgFromBytes(res.Content);
                return response;
            }

            response.Success = true;
            response.Content = new StatementsResult(new Json.StringOfJSON(Encoding.UTF8.GetString(res.Content)));

            return response;
        }

        /// <inheritdoc />
        public async Task<ProfileKeysLRSResponse> RetrieveStateIds(Activity activity, Agent agent, Guid? registration = null)
		{
			// TODO: since param

            if (activity == null)
            {
                throw new ArgumentNullException(nameof(activity));
            }

            if (agent == null)
            {
                throw new ArgumentNullException(nameof(agent));
            }

            var queryParams = new Dictionary<string, string>
            {
                { "activityId", activity.Id },
                { "agent", agent.ToJSON(Version) }
            };

            if (registration != null)
            {
                queryParams.Add("registration", registration.ToString());
            }

            return await GetProfileKeys("activities/state", queryParams);
        }

		/// <inheritdoc />
		public async Task<StateLRSResponse> RetrieveState(string id, Activity activity, Agent agent, Guid? registration = null)
        {
            var response = new StateLRSResponse();

            var queryParams = new Dictionary<string, string>
            {
                { "stateId", id },
                { "activityId", activity.Id },
                { "agent", agent.ToJSON(Version) }
            };

            var state = new StateDocument
            {
                Id = id,
                Activity = activity,
                Agent = agent
            };

            if (registration != null)
            {
                queryParams.Add("registration", registration.ToString());
                state.Registration = registration;
            }

            var resp = await GetDocument("activities/state", queryParams, state);

            if (resp.Status != HttpStatusCode.OK && resp.Status != HttpStatusCode.NotFound)
            {
                response.Success = false;
                response.HttpException = resp.Exception;
                response.SetErrMsgFromBytes(resp.Content);
                return response;
            }

            response.Success = true;
            response.Content = state;

            return response;
        }

        /// <inheritdoc />
        public async Task<LRSResponse> SaveState(StateDocument state)
        {
            var queryParams = new Dictionary<string, string>
            {
                { "stateId", state.Id },
                { "activityId", state.Activity.Id },
                { "agent", state.Agent.ToJSON(Version) }
            };

            if (state.Registration != null)
            {
                queryParams.Add("registration", state.Registration.ToString());
            }

            return await SaveDocument("activities/state", queryParams, state, HttpMethod.Put);
        }

        /// <inheritdoc />
        public async Task<LRSResponse> DeleteState(StateDocument state)
        {
            var queryParams = new Dictionary<string, string>
            {
                { "stateId", state.Id },
                { "activityId", state.Activity.Id },
                { "agent", state.Agent.ToJSON(Version) }
            };

            if (state.Registration != null)
            {
                queryParams.Add("registration", state.Registration.ToString());
            }

            return await DeleteDocument("activities/state", queryParams);
        }

        /// <inheritdoc />
        public async Task<LRSResponse> ClearState(Activity activity, Agent agent, Guid? registration = null)
        {
            var queryParams = new Dictionary<string, string>
            {
                { "activityId", activity.Id },
                { "agent", agent.ToJSON(Version) }
            };

            if (registration != null)
            {
                queryParams.Add("registration", registration.ToString());
            }

            return await DeleteDocument("activities/state", queryParams);
        }

		/// <inheritdoc />
		public async Task<ProfileKeysLRSResponse> RetrieveActivityProfileIds(Activity activity)
		{
			// TODO: since param

			var queryParams = new Dictionary<string, string>
            {
                { "activityId", activity.Id.ToString() }
            };

            return await GetProfileKeys("activities/profile", queryParams);
        }

		/// <inheritdoc />
		public async Task<ActivityProfileLRSResponse> RetrieveActivityProfile(string id, Activity activity)
        {
            var response = new ActivityProfileLRSResponse();

            var queryParams = new Dictionary<string, string>
            {
                { "profileId", id },
                { "activityId", activity.Id }
            };

            var profile = new ActivityProfileDocument
            {
                Id = id,
                Activity = activity
            };

            var resp = await GetDocument("activities/profile", queryParams, profile);

            if (resp.Status != HttpStatusCode.OK && resp.Status != HttpStatusCode.NotFound)
            {
                response.Success = false;
                response.HttpException = resp.Exception;
                response.SetErrMsgFromBytes(resp.Content);
                return response;
            }

            response.Success = true;
            response.Content = profile;

            return response;
        }

		/// <inheritdoc />
		public async Task<LRSResponse> SaveActivityProfile(ActivityProfileDocument profile)
        {
            var queryParams = new Dictionary<string, string>
            {
                { "profileId", profile.Id },
                { "activityId", profile.Activity.Id }
            };

            return await SaveDocument("activities/profile", queryParams, profile, HttpMethod.Put);
        }

		/// <inheritdoc />
		public async Task<LRSResponse> DeleteActivityProfile(ActivityProfileDocument profile)
        {
            var queryParams = new Dictionary<string, string>
            {
                { "profileId", profile.Id },
                { "activityId", profile.Activity.Id }
            };

            // TODO: need to pass Etag?

            return await DeleteDocument("activities/profile", queryParams);
        }

		/// <inheritdoc />
		public async Task<ProfileKeysLRSResponse> RetrieveAgentProfileIds(Agent agent)
        {
			// TODO: since param
			var queryParams = new Dictionary<string, string>
            {
                { "agent", agent.ToJSON(Version) }
            };

            return await GetProfileKeys("agents/profile", queryParams);
        }

		/// <inheritdoc />
		public async Task<AgentProfileLRSResponse> RetrieveAgentProfile(string id, Agent agent)
        {
            var response = new AgentProfileLRSResponse();

            var queryParams = new Dictionary<string, string>
            {
                { "profileId", id },
                { "agent", agent.ToJSON(Version) }
            };

            var profile = new AgentProfileDocument
            {
                Id = id,
                Agent = agent
            };

            var resp = await GetDocument("agents/profile", queryParams, profile);

            if (resp.Status != HttpStatusCode.OK && resp.Status != HttpStatusCode.NotFound)
            {
                response.Success = false;
                response.HttpException = resp.Exception;
                response.SetErrMsgFromBytes(resp.Content);
                return response;
            }

            response.Success = true;
            response.Content = profile;

            return response;
        }

		/// <inheritdoc />
		public async Task<LRSResponse> SaveAgentProfile(AgentProfileDocument profile)
        {
            return await SaveAgentProfile(profile, HttpMethod.Put);
        }

		/// <inheritdoc />
		public async Task<LRSResponse> ForceSaveAgentProfile(AgentProfileDocument profile)
        {
            return await SaveAgentProfile(profile, HttpMethod.Post);
        }

        /// <inheritdoc />
        public async Task<LRSResponse> DeleteAgentProfile(AgentProfileDocument profile)
        {
            var queryParams = new Dictionary<string, string>
            {
                { "profileId", profile.Id },
                { "agent", profile.Agent.ToJSON(Version) }
            };

            // TODO: need to pass Etag?

            return await DeleteDocument("agents/profile", queryParams);
        }

        /// <summary>
        /// Returns a <see cref="T:System.String"/> that represents the current <see cref="T:TinCan.RemoteLRS"/>.
        /// </summary>
        /// <returns>A <see cref="T:System.String"/> that represents the current <see cref="T:TinCan.RemoteLRS"/>.</returns>
        public override string ToString()
        {
            return string.Format("[RemoteLRS: Endpoint={0}, Version={1}, Auth={2}, Extended={3}]",
                                 Endpoint, Version, Auth, Extended);
        }

        async Task<LRSHttpResponse> MakeRequest(LRSHttpRequest request)
        {
            if (request == null)
            {
                throw new ArgumentNullException(nameof(request));
            }

            string url;

            if (request.Resource.StartsWith("http", StringComparison.InvariantCultureIgnoreCase))
            {
                url = request.Resource;
            }
            else
            {
                url = Endpoint.ToString();

                if (!url.EndsWith("/", StringComparison.Ordinal) && !request.Resource.StartsWith("/", StringComparison.Ordinal))
                {
                    url += "/";
                }

                url += request.Resource;
            }

            if (request.QueryParams != null)
            {
                var queryString = string.Empty;

                foreach (var entry in request.QueryParams)
                {
                    if (queryString != "")
                    {
                        queryString += "&";
                    }

                    queryString += string.Format("{0}={1}", WebUtility.UrlEncode(entry.Key), WebUtility.UrlEncode(entry.Value));
                }

                if (!string.IsNullOrEmpty(queryString))
                {
                    url += string.Format("?{0}", queryString);
                }
            }

            // TODO: handle special properties we recognize, such as content type, modified since, etc.
            var webReq = new HttpRequestMessage(request.Method, new Uri(url));

            client.DefaultRequestHeaders.Clear();
            client.DefaultRequestHeaders.Add("X-Experience-API-Version", Version.ToString());
            client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(request.ContentType ?? "application/content-stream"));

            if (Auth != null)
            {
                client.DefaultRequestHeaders.Add("Authorization", Auth);
            }

            if (request.Headers != null)
            {
                foreach (var entry in request.Headers)
                {
                    client.DefaultRequestHeaders.Add(entry.Key, entry.Value);
                }
            }

            if (request.Content != null)
            {
                webReq.Content = new ByteArrayContent(request.Content);
                webReq.Content.Headers.Add("Content-Length", request.Content.Length.ToString());

                if (!string.IsNullOrWhiteSpace(request.ContentType))
                {
                    webReq.Content.Headers.Add("Content-Type", request.ContentType);
                }
                else
                {
                    webReq.Content.Headers.Add("Content-Type", "application/json");
                }
            }

            LRSHttpResponse response;

            try
            {
                var theResponse = await client.SendAsync(webReq).ConfigureAwait(false);
                response = new LRSHttpResponse(theResponse);
            }
            catch (WebException exception)
            {
                response = new LRSHttpResponse();

                if (exception.Response != null)
                {
                    using (var stream = exception.Response.GetResponseStream())
                    {
                        response.Content = ReadFully(stream, (int)exception.Response.ContentLength);
                    }
                }
                else
                {
                    response.Content = Encoding.UTF8.GetBytes("Web exception without '.Response'");
                }

                response.Exception = exception;
            }

            return response;
        }

        /// <summary>
        /// See http://www.yoda.arachsys.com/csharp/readbinary.html no license found
        ///
        /// Reads data from a stream until the end is reached. The
        /// data is returned as a byte array. An IOException is
        /// thrown if any of the underlying IO calls fail.
        /// </summary>
        /// <param name="stream">The stream to read data from</param>
        /// <param name="initialLength">The initial buffer length</param>
        static byte[] ReadFully(Stream stream, int initialLength)
        {
            // If we've been passed an unhelpful initial length, just
            // use 32K.
            if (initialLength < 1)
            {
                initialLength = 32768;
            }

            var buffer = new byte[initialLength];
            var read = 0;

            int chunk;
            while ((chunk = stream.Read(buffer, read, buffer.Length - read)) > 0)
            {
                read += chunk;

                // If we've reached the end of our buffer, check to see if there's
                // any more information
                if (read == buffer.Length)
                {
                    int nextByte = stream.ReadByte();

                    // End of stream? If so, we're done
                    if (nextByte == -1)
                    {
                        return buffer;
                    }

                    // Nope. Resize the buffer, put in the byte we've just
                    // read, and continue
                    var newBuffer = new byte[buffer.Length * 2];
                    Array.Copy(buffer, newBuffer, buffer.Length);
                    newBuffer[read] = (byte)nextByte;
                    buffer = newBuffer;
                    read++;
                }
            }

            // Buffer is now too big. Shrink it.
            var ret = new byte[read];
            Array.Copy(buffer, ret, read);
            return ret;
        }

        async Task<LRSHttpResponse> GetDocument(string resource, Dictionary<string, string> queryParams, Document document)
        {
            var req = new LRSHttpRequest
            {
                Method = HttpMethod.Get,
                Resource = resource,
                QueryParams = queryParams
            };

            var response = await MakeRequest(req);

            if (response.Status == HttpStatusCode.OK)
            {
                document.Content = response.Content;
                document.ContentType = response.ContentType;
                document.Timestamp = response.LastModified;
                document.Etag = response.Etag;
            }

            return response;
        }

        async Task<ProfileKeysLRSResponse> GetProfileKeys(string resource, Dictionary<string, string> queryParams)
        {
            var response = new ProfileKeysLRSResponse();

            var request = new LRSHttpRequest
            {
                Method = HttpMethod.Get,
                Resource = resource,
                QueryParams = queryParams
            };

            var res = await MakeRequest(request);

            if (res.Status != HttpStatusCode.OK)
            {
                response.Success = false;
                response.HttpException = res.Exception;
                response.SetErrMsgFromBytes(res.Content);
                return response;
            }

            response.Success = true;

            var keys = JArray.Parse(Encoding.UTF8.GetString(res.Content));

            if (keys.Count > 0)
            {
                response.Content = new List<string>();

                foreach (JToken key in keys)
                {
                    response.Content.Add((string)key);
                }
            }

            return response;
        }

        async Task<LRSResponse> SaveDocument(string resource, Dictionary<string, string> queryParams, Document document, HttpMethod method)
        {
            var response = new LRSResponse();

            var request = new LRSHttpRequest
            {
                Method = method,
                Resource = resource,
                QueryParams = queryParams,
                ContentType = document.ContentType,
                Content = document.Content
            };

            var res = await MakeRequest(request);

            if (res.Status != HttpStatusCode.NoContent)
            {
                response.Success = false;
                response.HttpException = res.Exception;
                response.SetErrMsgFromBytes(res.Content);
                return response;
            }

            response.Success = true;

            return response;
        }

        async Task<LRSResponse> DeleteDocument(string resource, Dictionary<string, string> queryParams)
        {
            var response = new LRSResponse();

            var request = new LRSHttpRequest
            {
                Method = HttpMethod.Delete,
                Resource = resource,
                QueryParams = queryParams
            };

            var res = await MakeRequest(request);

            if (res.Status != HttpStatusCode.NoContent)
            {
                response.Success = false;
                response.HttpException = res.Exception;
                response.SetErrMsgFromBytes(res.Content);
                return response;
            }

            response.Success = true;

            return response;
        }

        async Task<StatementLRSResponse> GetStatement(Dictionary<String, String> queryParams)
        {
            var response = new StatementLRSResponse();

            var request = new LRSHttpRequest
            {
                Method = HttpMethod.Get,
                Resource = "statements",
                QueryParams = queryParams
            };

            var res = await MakeRequest(request);

            if (res.Status != HttpStatusCode.OK)
            {
                response.Success = false;
                response.HttpException = res.Exception;
                response.SetErrMsgFromBytes(res.Content);
                return response;
            }

            response.Success = true;
            response.Content = new Statement(new Json.StringOfJSON(Encoding.UTF8.GetString(res.Content)));

            return response;
        }

        async Task<LRSResponse> SaveAgentProfile(AgentProfileDocument profile, HttpMethod method)
		{
			var queryParams = new Dictionary<string, string>
			{
				{ "profileId", profile.Id },
				{ "agent", profile.Agent.ToJSON(Version) }
			};

			return await SaveDocument("agents/profile", queryParams, profile, method);
		}
    }
}
<file_sep>/TinCan/LRSResponses/LRSResponse.cs
/*
    Copyright 2014-2017 <NAME>

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
*/

using System;
using System.Text;

namespace TinCan.LRSResponses
{
	/// <summary>
	/// LRS response object.
	/// This isn't abstract because some responses for an LRS won't have content.
	/// In those cases we can get by just returning this base response.
	/// </summary>
	public class LRSResponse
    {
        /// <summary>
        /// Gets or sets a value indicating whether this <see cref="T:TinCan.LRSResponses.LRSResponse"/> was successful.
        /// </summary>
        /// <value><c>true</c> if success; otherwise, <c>false</c>.</value>
        public bool Success { get; set; }

		/// <summary>
		/// Gets or sets the HTTP exception, if one exists.
        /// This should only be non-null if the Success property is false.
		/// </summary>
		/// <value>The HTTP exception.</value>
		public Exception HttpException { get; set; }

		/// <summary>
		/// Gets or sets the error message, if one exists.
		/// This should only be non-null if the Success property is false.
		/// </summary>
		/// <value>The error message.</value>
		public string ErrorMessage { get; set; }

        /// <summary>
        /// Sets the error message from bytes.
        /// </summary>
        /// <param name="content">Content.</param>
        public void SetErrMsgFromBytes(byte[] content)
        {
            if (content == null)
            {
                throw new ArgumentNullException(nameof(content));
            }

            ErrorMessage = Encoding.UTF8.GetString(content);
        }

        /// <summary>
        /// Returns a <see cref="T:System.String"/> that represents the current <see cref="T:TinCan.LRSResponses.LRSResponse"/>.
        /// </summary>
        /// <returns>A <see cref="T:System.String"/> that represents the current <see cref="T:TinCan.LRSResponses.LRSResponse"/>.</returns>
        public override string ToString()
        {
            return string.Format("[LRSResponse: Success={0}, HttpException={1}, ErrorMessage={2}]",
                                 Success, HttpException, ErrorMessage);
        }
    }
}
<file_sep>/TinCan/Agent.cs
/*
    Copyright 2014-2017 <NAME>

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
*/

using Newtonsoft.Json.Linq;
using TinCan.Json;

namespace TinCan
{
    /// <summary>
    /// An Agent (an individual) is a persona or system.
    /// </summary>
    public class Agent : JsonModel, IStatementTarget
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="T:TinCan.Agent"/> class.
        /// </summary>
        public Agent() { }

		/// <summary>
		/// Initializes a new instance of the <see cref="T:TinCan.Agent"/> class.
		/// </summary>
		/// <param name="json">String of JSON describing the object.</param>
		public Agent(StringOfJSON json) : this(json.ToJObject()) { }

		/// <summary>
		/// Initializes a new instance of the <see cref="T:TinCan.Agent"/> class.
		/// </summary>
		/// <param name="jobj">JSON object describing the object.</param>
		public Agent(JObject jobj)
        {
            if (jobj["name"] != null)
            {
                Name = jobj.Value<string>("name");
            }

            if (jobj["mbox"] != null)
            {
                Mbox = jobj.Value<string>("mbox");
            }

            if (jobj["mbox_sha1sum"] != null)
            {
                MboxSha1Sum = jobj.Value<string>("mbox_sha1sum");
            }

            if (jobj["openid"] != null)
            {
                OpenId = jobj.Value<string>("openid");
            }

            if (jobj["account"] != null)
            {
                Account = (AgentAccount)jobj.Value<JObject>("account");
            }
        }

        /// <summary>
        /// Gets the type of the object.
        /// </summary>
        /// <value>The type of the object.</value>
        public virtual string ObjectType
        {
            get
            {
                return TypeName;
            }
        }

        /// <summary>
        /// Gets or sets the name.
        /// </summary>
        /// <value>The name.</value>
        public string Name { get; set; }

        /// <summary>
        /// Gets or sets the mbox.
        /// </summary>
        /// <value>The mbox.</value>
        public string Mbox { get; set; }

        /// <summary>
        /// Gets or sets the mbox sha1sum.
        /// </summary>
        /// <value>The mbox sha1sum.</value>
        public string MboxSha1Sum { get; set; }

        /// <summary>
        /// Gets or sets the openid.
        /// </summary>
        /// <value>The openid.</value>
        public string OpenId { get; set; }

        /// <summary>
        /// Gets or sets the account.
        /// </summary>
        /// <value>The account.</value>
        public AgentAccount Account { get; set; }

        /// <inheritdoc />
        public override JObject ToJObject(TCAPIVersion version)
        {
            var result = new JObject
            {
                { "objectType", ObjectType }
            };

            if (Name != null)
            {
                result.Add("name", Name);
            }

            if (Account != null)
            {
                result.Add("account", Account.ToJObject(version));
            }
            else if (Mbox != null)
            {
                result.Add("mbox", Mbox);
            }
            else if (MboxSha1Sum != null)
            {
                result.Add("mbox_sha1sum", MboxSha1Sum);
            }
            else if (OpenId != null)
            {
                result.Add("openid", OpenId);
            }

            return result;
        }

        /// <summary>
        /// Returns a <see cref="T:System.String"/> that represents the current <see cref="T:TinCan.Agent"/>.
        /// </summary>
        /// <returns>A <see cref="T:System.String"/> that represents the current <see cref="T:TinCan.Agent"/>.</returns>
        public override string ToString()
        {
            return string.Format("[Agent: Name={0}, Mbox={1}, MboxSha1Sum={2}, OpenId={3}, Account={4}]",
                                 Name, Mbox, MboxSha1Sum, OpenId, Account);
        }

		/// <summary>
		/// The name of this object type.
		/// </summary>
		public static string TypeName = nameof(Agent);

        /// <summary>
        /// Defines the operation to use when casting from a JObject to this type.
        /// </summary>
        /// <returns>The JObject as this type.</returns>
        /// <param name="jobj">The JObject to cast.</param>
        public static explicit operator Agent(JObject jobj)
        {
            return new Agent(jobj);
        }
    }
}
<file_sep>/TinCan/Statement.cs
/*
    Copyright 2014-2017 <NAME>

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
*/

using System;
using Newtonsoft.Json.Linq;
using TinCan.Json;

namespace TinCan
{
	/// <summary>
	/// The basic communication mechanism of the Experience API.
	/// </summary>
	public class Statement : StatementBase
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="T:TinCan.Statement"/> class.
        /// </summary>
        public Statement() { }

        /// <summary>
        /// Initializes a new instance of the <see cref="T:TinCan.Statement"/> class.
        /// </summary>
        /// <param name="json">Json.</param>
        public Statement(StringOfJSON json) : this(json.ToJObject()) { }

        /// <summary>
        /// Initializes a new instance of the <see cref="T:TinCan.Statement"/> class.
        /// </summary>
        /// <param name="jobj">Jobj.</param>
        public Statement(JObject jobj) : base(jobj)
        {
            if (jobj["id"] != null)
            {
                Id = new Guid(jobj.Value<string>("id"));
            }

            if (jobj["stored"] != null)
            {
                Stored = jobj.Value<DateTime>("stored");
            }

            if (jobj["authority"] != null)
            {
                Authority = (Agent)jobj.Value<JObject>("authority");
            }

            if (jobj["version"] != null)
            {
                Version = (TCAPIVersion)jobj.Value<string>("version");
            }

            // handle SubStatement as target which isn't provided by StatementBase
            // because SubStatements are not allowed to nest
            if (jobj["object"] != null && (string)jobj["object"]["objectType"] == SubStatement.TypeName)
            {
                Target = (SubStatement)jobj.Value<JObject>("object");
            }
        }

        /// <summary>
        /// Gets or sets the unique identifier of this statement.
        /// </summary>
        /// <value>The identifier.</value>
        public Guid? Id { get; set; }

        /// <summary>
        /// Gets or sets the time this statement was stored.
        /// </summary>
        /// <value>The stored time.</value>
        public DateTime? Stored { get; set; }

		/// <summary>
		/// Gets or sets information about whom or what has asserted that this Statement is true.
		/// </summary>
		/// <value>The authority.</value>
		public Agent Authority { get; set; }

        /// <summary>
        /// Gets or sets the xAPI version.
        /// </summary>
        /// <value>The version.</value>
        public TCAPIVersion Version { get; set; }

        /// <inheritdoc />
        public override JObject ToJObject(TCAPIVersion version)
        {
            var result = base.ToJObject(version);

            if (Id != null)
            {
                result.Add("id", Id.ToString());
            }

            if (Stored != null)
            {
                result.Add("stored", Stored.Value.ToString(TimeFormat.Default));
            }

            if (Authority != null)
            {
                result.Add("authority", Authority.ToJObject(version));
            }

            if (version != null)
            {
                result.Add("version", version.ToString());
            }

            return result;
        }

        /// <summary>
        /// Stamp this instance with a unique identifier and timestamp.
        /// </summary>
        public void Stamp()
        {
            if (Id == null)
            {
                Id = Guid.NewGuid();
            }

            if (Timestamp == null)
            {
                Timestamp = DateTime.UtcNow;
            }
        }

        /// <summary>
        /// Returns a <see cref="T:System.String"/> that represents the current <see cref="T:TinCan.Statement"/>.
        /// </summary>
        /// <returns>A <see cref="T:System.String"/> that represents the current <see cref="T:TinCan.Statement"/>.</returns>
        public override string ToString()
        {
            return string.Format("[Statement: Actor={0}, Verb={1}, Target={2}, Result={3}, Context={4}, Timestamp={5}, Id={6}, Stored={7}, Authority={8}, Version={9}]",
                                 Actor, Verb, Target, Result, Context, Timestamp, Id, Stored, Authority, Version);
        }

		/// <summary>
		/// Defines the operation to use when casting from a JObject to this type.
		/// </summary>
		/// <returns>The JObject as this type.</returns>
		/// <param name="jobj">The JObject to cast.</param>
		public static explicit operator Statement(JObject jobj)
        {
            return new Statement(jobj);
        }
    }
}
<file_sep>/TinCan/Extensions.cs
/*
    Copyright 2014-2017 <NAME>

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
*/

using System;
using System.Collections.Generic;
using Newtonsoft.Json.Linq;
using TinCan.Json;

namespace TinCan
{
	/// <summary>
	/// A map of any other domain-specific context relevant to a Statement.
	/// </summary>
	public class Extensions : JsonModel
    {
        Dictionary<Uri, JToken> map;

        /// <summary>
        /// Initializes a new instance of the <see cref="T:TinCan.Extensions"/> class.
        /// </summary>
        public Extensions()
        {
            map = new Dictionary<Uri, JToken>();
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="T:TinCan.Extensions"/> class.
        /// </summary>
        /// <param name="jobj">Jobj.</param>
        public Extensions(JObject jobj) : this()
        {
            foreach (var item in jobj)
            {
                map.Add(new Uri(item.Key), item.Value);
            }
		}

		/// <summary>
        /// Gets a value indicating whether this <see cref="T:TinCan.Extensions"/> is empty.
        /// </summary>
        /// <value><c>true</c> if is empty; otherwise, <c>false</c>.</value>
		public bool IsEmpty
		{
            get
            {
                return map.Count <= 0;
            }
		}

        /// <inheritdoc />
        public override JObject ToJObject(TCAPIVersion version)
        {
            JObject result = new JObject();

            foreach (var entry in map)
            {
                result.Add(entry.Key.ToString(), entry.Value);
            }

            return result;
        }

        /// <summary>
        /// Returns a <see cref="T:System.String"/> that represents the current <see cref="T:TinCan.Extensions"/>.
        /// </summary>
        /// <returns>A <see cref="T:System.String"/> that represents the current <see cref="T:TinCan.Extensions"/>.</returns>
        public override string ToString()
        {
            return string.Format("[Extensions: IsEmpty={0}, Map={1}]",
                                 IsEmpty, map);
        }

		/// <summary>
		/// Defines the operation to use when casting from a JObject to this type.
		/// </summary>
		/// <returns>The JObject as this type.</returns>
		/// <param name="jobj">The JObject to cast.</param>
		public static explicit operator Extensions(JObject jobj)
        {
            return new Extensions(jobj);
        }
    }
}
<file_sep>/TinCan/Documents/ActivityProfileDocument.cs
/*
    Copyright 2014-2017 <NAME>

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
*/

using System.Text;

namespace TinCan.Documents
{
	/// <summary>
	/// A document where information about an activity is kept.
	/// </summary>
	public class ActivityProfileDocument : Document
    {
        /// <summary>
        /// Gets or sets the activity.
        /// </summary>
        /// <value>The activity.</value>
        public Activity Activity { get; set; }

        /// <summary>
        /// Returns a <see cref="T:System.String"/> that represents the current <see cref="T:TinCan.Documents.ActivityProfileDocument"/>.
        /// </summary>
        /// <returns>A <see cref="T:System.String"/> that represents the current <see cref="T:TinCan.Documents.ActivityProfileDocument"/>.</returns>
		public override string ToString()
		{
			return string.Format("[StateDocument: Id={0}, Etag={1}, Timestamp={2}, ContentType={3}, Content={4}, Activity={5}]",
								 Id, Etag, Timestamp, ContentType, Encoding.UTF8.GetString(Content, 0, Content.Length), Activity);
		}
    }
}
<file_sep>/TinCan/TCAPIVersion.cs
/*
    Copyright 2014-2017 <NAME>

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
*/

using System;
using System.Collections.Generic;

namespace TinCan
{
    /// <summary>
    /// TCAPIV ersion.
    /// </summary>
    public sealed class TCAPIVersion
    {
        /// <summary>
        /// The v103.
        /// </summary>
        public static readonly TCAPIVersion V103 = new TCAPIVersion("1.0.3");

        /// <summary>
        /// The v102.
        /// </summary>
        public static readonly TCAPIVersion V102 = new TCAPIVersion("1.0.2");

        /// <summary>
        /// The v101.
        /// </summary>
        public static readonly TCAPIVersion V101 = new TCAPIVersion("1.0.1");

        /// <summary>
        /// The v100.
        /// </summary>
        public static readonly TCAPIVersion V100 = new TCAPIVersion("1.0.0");

        /// <summary>
        /// The v095.
        /// </summary>
        public static readonly TCAPIVersion V095 = new TCAPIVersion("0.95");

        /// <summary>
        /// The v090.
        /// </summary>
        public static readonly TCAPIVersion V090 = new TCAPIVersion("0.9");

        /// <summary>
        /// Latest this instance.
        /// </summary>
        /// <returns>The latest.</returns>
        public static TCAPIVersion Latest
        {
            get
            {
                return V103;
            }
        }

        static Dictionary<string, TCAPIVersion> known;
        static Dictionary<string, TCAPIVersion> supported;

        /// <summary>
        /// Gets the known.
        /// </summary>
        /// <returns>The known.</returns>
        public static Dictionary<string, TCAPIVersion> Known
        {
            get
            {
                if (known != null)
                {
                    return known;
                }

                known = new Dictionary<string, TCAPIVersion>
                {
                    { V103.text, V103 },
                    { V102.text, V102 },
                    { V101.text, V101 },
                    { V100.text, V100 },
                    { V095.text, V095 },
                    { V090.text, V090 }
                };

                return known;
            }
        }

        /// <summary>
        /// Gets the supported.
        /// </summary>
        /// <value>The supported.</value>
        public static Dictionary<string, TCAPIVersion> Supported
        {
            get
            {
                if (supported != null)
                {
                    return supported;
                }

                supported = new Dictionary<string, TCAPIVersion>
                {
                    { V103.text, V103 },
                    { V102.text, V102 },
                    { V101.text, V101 },
                    { V100.text, V100 }
                };

                return supported;
            }
        }

        /// <summary>
        /// Ops the explicit.
        /// </summary>
        /// <returns>The explicit.</returns>
        /// <param name="vStr">V string.</param>
        public static explicit operator TCAPIVersion(string vStr)
        {
            var knownVersions = Known;

            if (!knownVersions.ContainsKey(vStr))
            {
                throw new ArgumentException("Unrecognized version: " + vStr);
            }

            return knownVersions[vStr];
        }

        readonly string text;

        /// <summary>
        /// Initializes a new instance of the <see cref="T:TinCan.TCAPIVersion"/> class.
        /// </summary>
        /// <param name="value">Value.</param>
        TCAPIVersion(string value)
        {
            text = value;
        }

        /// <summary>
        /// Returns a <see cref="T:System.String"/> that represents the current <see cref="T:TinCan.TCAPIVersion"/>.
        /// </summary>
        /// <returns>A <see cref="T:System.String"/> that represents the current <see cref="T:TinCan.TCAPIVersion"/>.</returns>
        public override string ToString()
        {
            return text;
        }
    }
}
<file_sep>/TinCan/Activity.cs
/*
    Copyright 2014-2017 <NAME>

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
*/

using System;
using Newtonsoft.Json.Linq;
using TinCan.Json;

namespace TinCan
{
    /// <summary>
    /// The base xAPI object.
    /// </summary>
    public class Activity : JsonModel, IStatementTarget
    {
        string id;

        /// <summary>
        /// Initializes a new instance of the <see cref="T:TinCan.Activity"/> class.
        /// </summary>
        public Activity() { }

        /// <summary>
        /// Initializes a new instance of the <see cref="T:TinCan.Activity"/> class.
        /// </summary>
        /// <param name="json">Json.</param>
        public Activity(StringOfJSON json) : this(json.ToJObject()) { }

        /// <summary>
        /// Initializes a new instance of the <see cref="T:TinCan.Activity"/> class.
        /// </summary>
        /// <param name="jobj">The JSON object describing the activity.</param>
        public Activity(JObject jobj)
        {
            if (jobj == null)
            {
                throw new ArgumentNullException(nameof(jobj));
            }

            if (jobj["id"] != null)
            {
                var idFromJSON = jobj.Value<string>("id");
                Id = idFromJSON;
            }

            if (jobj["definition"] != null)
            {
                Definition = (ActivityDefinition)jobj.Value<JObject>("definition");
            }
        }

		/// <inheritdoc />
		public string ObjectType
		{
			get
			{
				return TypeName;
			}
		}

        /// <summary>
        /// Gets or sets the activity identifier.
        /// </summary>
        /// <value>The identifier.</value>
        public string Id
        {
            get
            {
                return id;
            }

            set
            {
                var uri = new Uri(value);
                id = value;
            }
        }

        /// <summary>
        /// Gets or sets the activity definition.
        /// </summary>
        /// <value>The definition.</value>
        public ActivityDefinition Definition { get; set; }

        /// <inheritdoc />
        public override JObject ToJObject(TCAPIVersion version)
        {
            var result = new JObject
            {
                { "objectType", ObjectType }
            };

            if (Id != null)
            {
                result.Add("id", Id);
            }

            if (Definition != null)
            {
                result.Add("definition", Definition.ToJObject(version));
            }

            return result;
        }

        /// <summary>
        /// Returns a <see cref="T:System.String"/> that represents the current <see cref="T:TinCan.Activity"/>.
        /// </summary>
        /// <returns>A <see cref="T:System.String"/> that represents the current <see cref="T:TinCan.Activity"/>.</returns>
        public override string ToString()
        {
            return string.Format("[Activity: ObjectType={0}, Id={1}, Definition={2}]", ObjectType, Id, Definition);
        }

		/// <summary>
		/// The name of this object type.
		/// </summary>
		public static string TypeName = nameof(Activity);

		/// <summary>
		/// Defines the operation to use when casting from a JObject to this type.
		/// </summary>
		/// <returns>The JObject as this type.</returns>
		/// <param name="jobj">The JObject to cast.</param>
		public static explicit operator Activity(JObject jobj)
        {
            return new Activity(jobj);
        }
    }
}
<file_sep>/TinCan/Documents/Document.cs
/*
    Copyright 2014-2017 <NAME>

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
*/

using System;
using System.Text;

namespace TinCan.Documents
{
	/// <summary>
	/// A construct where information about the learner or activity is kept.
	/// </summary>
	public abstract class Document
    {
        /// <summary>
        /// Gets or sets the identifier.
        /// </summary>
        /// <value>The identifier.</value>
        public string Id { get; set; }

        /// <summary>
        /// Gets or sets the Etag.
        /// </summary>
        /// <value>An opaque identifier assigned by a web server to a specific version of a resource.</value>
        public string Etag { get; set; }

        /// <summary>
        /// Gets or sets the timestamp.
        /// </summary>
        /// <value>The timestamp.</value>
        public DateTime Timestamp { get; set; }

        /// <summary>
        /// Gets or sets the type of the content.
        /// </summary>
        /// <value>The type of the content.</value>
        public string ContentType { get; set; }

        /// <summary>
        /// Gets or sets the content.
        /// </summary>
        /// <value>The content.</value>
        public byte[] Content { get; set; }

        /// <summary>
        /// Returns a <see cref="T:System.String"/> that represents the current <see cref="T:TinCan.Documents.Document"/>.
        /// </summary>
        /// <returns>A <see cref="T:System.String"/> that represents the current <see cref="T:TinCan.Documents.Document"/>.</returns>
        public override string ToString()
        {
            return string.Format("[Document: Id={0}, Etag={1}, Timestamp={2}, ContentType={3}, Content={4}]",
                                 Id, Etag, Timestamp, ContentType, Encoding.UTF8.GetString(Content, 0, Content.Length));
        }
    }
}
<file_sep>/TinCanTests/RemoteLRSTest.cs
/*
    Copyright 2014-2017 <NAME>

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
*/

using System;
using NUnit.Framework;
using TinCan;

namespace TinCanTests
{
    [TestFixture]
    class RemoteLRSTest
    {
        [Test]
        public void TestBasicCtr()
        {
            var uri = new Uri("https://cloud.scorm.com/tc/U2S4SI5FY0/sandbox/");
            var username = "Nja986GYE1_XrWMmFUE";
            var password = "<PASSWORD>";

			var lrs = new RemoteLRS(
				uri,
				username,
				password
			);

            Assert.That(lrs.Endpoint, Is.EqualTo(uri));
            Assert.That(lrs.Auth, Is.Not.Null);
            Assert.That(lrs.Auth, Is.Not.Empty);
            Assert.That(lrs.Version, Is.EqualTo(TCAPIVersion.Latest));
            Assert.That(lrs.Extended, Is.Empty);
        }
    }
}
<file_sep>/TinCan/Verb.cs
/*
    Copyright 2014-2017 <NAME>

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
*/

using System;
using Newtonsoft.Json.Linq;
using TinCan.Json;

namespace TinCan
{
	/// <summary>
	/// The Verb defines the action between an Actor and an Activity.
	/// </summary>
	public class Verb : JsonModel
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="T:TinCan.Verb"/> class.
        /// </summary>
        public Verb() { }

        /// <summary>
        /// Initializes a new instance of the <see cref="T:TinCan.Verb"/> class.
        /// </summary>
        /// <param name="json">Json.</param>
        public Verb(StringOfJSON json) : this(json.ToJObject()) { }

        /// <summary>
        /// Initializes a new instance of the <see cref="T:TinCan.Verb"/> class.
        /// </summary>
        /// <param name="jobj">Jobj.</param>
        public Verb(JObject jobj)
        {
            if (jobj["id"] != null)
            {
                Id = new Uri(jobj.Value<string>("id"));
            }

            if (jobj["display"] != null)
            {
                Display = (LanguageMap)jobj.Value<JObject>("display");
            }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="T:TinCan.Verb"/> class.
        /// </summary>
        /// <param name="uri">URI.</param>
        public Verb(Uri uri)
        {
            Id = uri;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="T:TinCan.Verb"/> class.
        /// </summary>
        /// <param name="uri">URI.</param>
        /// <param name="defaultLanguage">Default language.</param>
        /// <param name="defaultTerm">Default term.</param>
        public Verb(Uri uri, string defaultLanguage, string defaultTerm)
        {
            Id = uri;

            Display = new LanguageMap();
            Display.Add(defaultLanguage, defaultTerm);
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="T:TinCan.Verb"/> class.
        /// </summary>
        /// <param name="str">String.</param>
        public Verb(string str)
        {
            Id = new Uri(str);
        }

        /// <summary>
        /// Gets or sets a value which corresponds to a Verb definition.
        /// Each Verb definition corresponds to the meaning of a Verb, not the word.
        /// </summary>
        /// <value>The identifier.</value>
        public Uri Id { get; set; }

        /// <summary>
        /// Gets or sets the human readable representation of the Verb in one or more languages.
        /// </summary>
        /// <value>The human readable representation of the Verb in one or more languages.</value>
        public LanguageMap Display { get; set; }

        /// <summary>
        /// Tos the JO bject.
        /// </summary>
        /// <returns>The JO bject.</returns>
        /// <param name="version">Version.</param>
        public override JObject ToJObject(TCAPIVersion version)
        {
            var result = new JObject();

            if (Id != null)
            {
                result.Add("id", Id.ToString());
            }

            if (Display != null && ! Display.IsEmpty)
            {
                result.Add("display", Display.ToJObject(version));
            }

            return result;
        }

        /// <summary>
        /// Returns a <see cref="T:System.String"/> that represents the current <see cref="T:TinCan.Verb"/>.
        /// </summary>
        /// <returns>A <see cref="T:System.String"/> that represents the current <see cref="T:TinCan.Verb"/>.</returns>
        public override string ToString()
        {
            return string.Format("[Verb: Id={0}, Display={1}]", Id, Display);
        }

		/// <summary>
		/// Indicates the activity provider has determined that the session was abnormally terminated either by an actor or due to a system failure.
		/// </summary>
		public static readonly Verb Abandoned = FromName(nameof(Abandoned));

		/// <summary>
		/// Indicates the actor replied to a question, where the object is generally an activity representing the question. The text of the answer will often be included in the response inside result.
		/// </summary>
		public static readonly Verb Answered = FromName(nameof(Answered));

		/// <summary>
		/// Indicates an inquiry by an actor with the expectation of a response or answer to a question.
		/// </summary>
		public static readonly Verb Asked = FromName(nameof(Asked));

		/// <summary>
		/// Indicates the actor made an effort to access the object. An attempt statement without additional activities could be considered incomplete in some cases.
		/// </summary>
		public static readonly Verb Attempted = FromName(nameof(Attempted));

		/// <summary>
		/// Indicates the actor was present at a virtual or physical event or activity.
		/// </summary>
		public static readonly Verb Attended = FromName(nameof(Attended));

		/// <summary>
		/// Indicates the actor provided digital or written annotations on or about an object.
		/// </summary>
		public static readonly Verb Commented = FromName(nameof(Commented));

		/// <summary>
		/// Indicates the actor finished or concluded the activity normally.
		/// </summary>
		public static readonly Verb Completed = FromName(nameof(Completed));

		/// <summary>
		/// Indicates the actor intentionally departed from the activity or object.
		/// </summary>
		public static readonly Verb Exited = FromName(nameof(Exited));

		/// <summary>
		/// Indicates the actor only encountered the object, and is applicable in situations where a specific achievement or completion is not required.
		/// </summary>
		public static readonly Verb Experienced = FromName(nameof(Experienced));

		/// <summary>
		/// Indicates the actor did not successfully pass an activity to a level of predetermined satisfaction.
		/// </summary>
		public static readonly Verb Failed = FromName(nameof(Failed));

		/// <summary>
		/// Indicates the actor introduced an object into a physical or virtual location.
		/// </summary>
		public static readonly Verb Imported = FromName(nameof(Imported));

		/// <summary>
		/// Indicates the activity provider has determined that the actor successfully started an activity.
		/// </summary>
		public static readonly Verb Initialized = FromName(nameof(Initialized));

		/// <summary>
		/// Indicates the actor engaged with a physical or virtual object.
		/// </summary>
		public static readonly Verb Interacted = FromName(nameof(Interacted));

		/// <summary>
		/// Indicates the actor attempted to start an activity.
		/// </summary>
		public static readonly Verb Launched = FromName(nameof(Launched));

		/// <summary>
		/// Indicates the actor gained access to a system or service by identifying and authenticating with the credentials provided by the actor.
		/// </summary>
		public static readonly Verb LoggedIn = FromName(nameof(LoggedIn));

		/// <summary>
		/// Indicates the actor either lost or discontinued access to a system or service.
		/// </summary>
		public static readonly Verb LoggedOut = FromName(nameof(LoggedOut));

		/// <summary>
		/// Indicates the highest level of comprehension or competence the actor performed in an activity.
		/// </summary>
		public static readonly Verb Mastered = FromName(nameof(Mastered));

		/// <summary>
		/// Indicates the actor successfully passed an activity to a level of predetermined satisfaction.
		/// </summary>
		public static readonly Verb Passed = FromName(nameof(Passed));

		/// <summary>
		/// Indicates the selected choices, favored options or settings of an actor in relation to an object or activity.
		/// </summary>
		public static readonly Verb Preferred = FromName(nameof(Preferred));

		/// <summary>
		/// Indicates a value of how much of an actor has advanced or moved through an activity.
		/// </summary>
		public static readonly Verb Progressed = FromName(nameof(Progressed));

		/// <summary>
		/// Indicates the actor is officially enrolled or inducted in an activity.
		/// </summary>
		public static readonly Verb Registered = FromName(nameof(Registered));

		/// <summary>
		/// Indicates an actor reacted or replied to an object.
		/// </summary>
		public static readonly Verb Responded = FromName(nameof(Responded));

		/// <summary>
		/// Indicates the application has determined that the actor continued or reopened a suspended attempt on an activity.
		/// </summary>
		public static readonly Verb Resumed = FromName(nameof(Resumed));

		/// <summary>
		/// Indicates that the authority or activity provider determined the actor has fulfilled the criteria of the object or activity.
		/// </summary>
		public static readonly Verb Satisfied = FromName(nameof(Satisfied));

		/// <summary>
		/// Indicates a numerical value related to an actor's performance on an activity.
		/// </summary>
		public static readonly Verb Scored = FromName(nameof(Scored));

		/// <summary>
		/// Indicates the actor's intent to openly provide access to an object of common interest to other actors or groups.
		/// </summary>
		public static readonly Verb Shared = FromName(nameof(Shared));

		/// <summary>
		/// Indicates the status of a temporarily halted activity when an actor's intent is returning to the or object activity at a later time.
		/// </summary>
		public static readonly Verb Suspended = FromName(nameof(Suspended));

		/// <summary>
		/// Indicates that the actor successfully ended an activity.
		/// </summary>
		public static readonly Verb Terminated = FromName(nameof(Terminated));

		/// <summary>
		/// A special reserved verb used by a LRS or application to mark a statement as invalid.
        /// See the xAPI specification for details on Voided statements.
		/// </summary>
		public static readonly Verb Voided = FromName(nameof(Voided));

		/// <summary>
        /// Indicates that the learning activity requirements were met by means other than completing the activity.
        /// A waived statement is used to indicate that the activity may be skipped by the actor.
		/// </summary>
		public static readonly Verb Waived = FromName(nameof(Waived));

        /// <summary>
        /// Internal method to generate an xAPI verb from a string name.
        /// Defaults to "en-US".
        /// </summary>
        /// <returns>The new xAPI verb.</returns>
        /// <param name="name">String name of the xAPI verb. Will be converted to lowercase.</param>
        internal static Verb FromName(string name)
        {
            var lowercaseName = name.ToLower();
            return new Verb(new Uri(string.Format("http://adlnet.gov/expapi/verbs/{0}", lowercaseName)), "en-US", lowercaseName);
        }

		/// <summary>
		/// Defines the operation to use when casting from a JObject to this type.
		/// </summary>
		/// <returns>The JObject as this type.</returns>
		/// <param name="jobj">The JObject to cast.</param>
		public static explicit operator Verb(JObject jobj)
        {
            return new Verb(jobj);
        }
    }
}
<file_sep>/TinCan/ContextActivities.cs
/*
    Copyright 2014-2017 <NAME>

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
*/

using System.Collections.Generic;
using Newtonsoft.Json.Linq;
using TinCan.Json;

namespace TinCan
{
	/// <summary>
	/// A map of the types of learning activity context that a Statement is related to.
	/// </summary>
	public class ContextActivities : JsonModel
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="T:TinCan.ContextActivities"/> class.
        /// </summary>
        public ContextActivities() {}

        /// <summary>
        /// Initializes a new instance of the <see cref="T:TinCan.ContextActivities"/> class.
        /// </summary>
        /// <param name="json">Json.</param>
        public ContextActivities(StringOfJSON json): this(json.ToJObject()) {}

        /// <summary>
        /// Initializes a new instance of the <see cref="T:TinCan.ContextActivities"/> class.
        /// </summary>
        /// <param name="jobj">Jobj.</param>
        public ContextActivities(JObject jobj)
        {
            if (jobj["parent"] != null)
            {
                Parent = new List<Activity>();

                foreach (var jactivity in jobj["parent"])
                {
                    Parent.Add((Activity)jactivity);
                }
            }

            if (jobj["grouping"] != null)
            {
                Grouping = new List<Activity>();

                foreach (var jactivity in jobj["grouping"])
                {
                    Grouping.Add((Activity)jactivity);
                }
            }

            if (jobj["category"] != null)
            {
                Category = new List<Activity>();

                foreach (var jactivity in jobj["category"])
                {
                    Category.Add((Activity)jactivity);
                }
            }

            if (jobj["other"] != null)
            {
                Other = new List<Activity>();

                foreach (var jactivity in jobj["other"])
                {
                    Other.Add((Activity)jactivity);
                }
            }
        }

		/// <summary>
		/// Gets or sets the parent context types.
		/// </summary>
		/// <value>The relevant context types.</value>
		public List<Activity> Parent { get; set; }

		/// <summary>
		/// Gets or sets the grouping context types.
		/// </summary>
		/// <value>The relevant context types.</value>
		public List<Activity> Grouping { get; set; }

		/// <summary>
		/// Gets or sets the category context types.
		/// </summary>
		/// <value>The relevant context types.</value>
		public List<Activity> Category { get; set; }

		/// <summary>
		/// Gets or sets the other context types.
		/// </summary>
		/// <value>The relevant context types.</value>
		public List<Activity> Other { get; set; }

        /// <inheritdoc />
        public override JObject ToJObject(TCAPIVersion version)
        {
            var result = new JObject();

            if (Parent != null && Parent.Count > 0)
            {
                var jparent = new JArray();
                result.Add("parent", jparent);

                foreach (var activity in Parent)
                {
                    jparent.Add(activity.ToJObject(version));
                }
            }

            if (Grouping != null && Grouping.Count > 0)
            {
                var jgrouping = new JArray();
                result.Add("grouping", jgrouping);

                foreach (var activity in Grouping)
                {
                    jgrouping.Add(activity.ToJObject(version));
                }
            }

            if (Category != null && Category.Count > 0)
            {
                var jcategory = new JArray();
                result.Add("category", jcategory);

                foreach (var activity in Category)
                {
                    jcategory.Add(activity.ToJObject(version));
                }
            }

            if (Other != null && Other.Count > 0)
            {
                var jother = new JArray();
                result.Add("other", jother);

                foreach (var activity in Other)
                {
                    jother.Add(activity.ToJObject(version));
                }
            }

            return result;
        }

        /// <summary>
        /// Returns a <see cref="T:System.String"/> that represents the current <see cref="T:TinCan.ContextActivities"/>.
        /// </summary>
        /// <returns>A <see cref="T:System.String"/> that represents the current <see cref="T:TinCan.ContextActivities"/>.</returns>
        public override string ToString()
        {
            return string.Format("[ContextActivities: Parent={0}, Grouping={1}, Category={2}, Other={3}]",
                                 Parent, Grouping, Category, Other);
        }

		/// <summary>
		/// Defines the operation to use when casting from a JObject to this type.
		/// </summary>
		/// <returns>The JObject as this type.</returns>
		/// <param name="jobj">The JObject to cast.</param>
		public static explicit operator ContextActivities(JObject jobj)
        {
            return new ContextActivities(jobj);
        }
    }
}

branch_name: refs/heads/master

repo_language: C#