namespace Nessos.FsPickler.Json
    open System
    open System.Collections.Generic
    open System.Globalization
    open System.IO
    open System.Numerics
    open System.Text
    open Newtonsoft.Json
    open Nessos.FsPickler
    /// 
    ///     Json format deserializer
    /// 
    type internal JsonPickleReader (jsonReader : JsonReader, omitHeader, isTopLevelSequence, leaveOpen) =
        do
            jsonReader.CloseInput <- not leaveOpen
            jsonReader.SupportMultipleContent <- isTopLevelSequence
        let isBsonReader = match jsonReader with :? Bson.BsonReader -> true | _ -> false
        let mutable depth = 0
        let arrayStack = new Stack ()
        do arrayStack.Push Int32.MinValue
        // do not write tag if omitting header or array element
        let omitTag () = (omitHeader && depth = 0) || arrayStack.Peek() = depth - 1
        interface IPickleFormatReader with
            
            member __.BeginReadRoot (tag : string) =
                do jsonReader.Read() |> ignore
                    
                if omitHeader then () else
                if jsonReader.TokenType <> JsonToken.StartObject then raise <| new FormatException("invalid json root object.")
                else
                    do jsonReader.MoveNext()
                    let version = jsonReader.ReadPrimitiveAs false "FsPickler"
                    if version <> jsonFormatVersion then
                        let v = Version(version)
                        raise <| new FormatException(sprintf "Invalid FsPickler format version %O." version)
                    let sTag = jsonReader.ReadPrimitiveAs false "type"
                    if tag <> sTag then
                        raise <| new InvalidPickleTypeException(tag, sTag)
            member __.EndReadRoot () = 
                if not omitHeader then jsonReader.Read() |> ignore
            member __.BeginReadObject (tag : string) =
                
                if not <| omitTag () then
                    jsonReader.ReadProperty tag
                    jsonReader.MoveNext ()
                if isTopLevelSequence && depth = 0 then
                    arrayStack.Push depth
                    depth <- depth + 1
                    ObjectFlags.IsSequenceHeader
                else
                    match jsonReader.TokenType with
                    | JsonToken.Null -> ObjectFlags.IsNull
                    | JsonToken.StartArray ->
                        jsonReader.MoveNext()
                        arrayStack.Push depth
                        depth <- depth + 1
                        ObjectFlags.IsSequenceHeader
                    | JsonToken.StartObject ->
                        do jsonReader.MoveNext()
                        depth <- depth + 1
                        if jsonReader.ValueAs () = "_flags" then
                            jsonReader.MoveNext()
                            let csvFlags = jsonReader.ValueAs()
                            jsonReader.MoveNext()
                            parseFlagCsv csvFlags
                        else
                            ObjectFlags.None
                    | token -> raise <| new FormatException(sprintf "expected start of Json object but was '%O'." token)
            member __.EndReadObject () =
                if isTopLevelSequence && depth = 1 then
                    arrayStack.Pop () |> ignore
                    depth <- depth - 1
                    jsonReader.Read() |> ignore
                else
                    match jsonReader.TokenType with
                    | JsonToken.Null -> ()
                    | JsonToken.EndObject -> depth <- depth - 1
                    | JsonToken.EndArray ->
                        arrayStack.Pop() |> ignore
                        depth <- depth - 1
                    | token -> raise <| new FormatException(sprintf "expected end of Json object but was '%O'." token)
                    if omitHeader && depth = 0 then ()
                    else jsonReader.Read() |> ignore
            member __.SerializeUnionCaseNames = true
            member __.PreferLengthPrefixInSequences = false
            member __.ReadNextSequenceElement () = 
                if isTopLevelSequence && depth = 1 then
                    jsonReader.TokenType <> JsonToken.None
                else
                    jsonReader.TokenType <> JsonToken.EndArray
            member __.ReadCachedObjectId () = jsonReader.ReadPrimitiveAs false "id"
            member __.ReadBoolean tag = jsonReader.ReadPrimitiveAs (omitTag ()) tag
            member __.ReadByte tag = jsonReader.ReadPrimitiveAs (omitTag ()) tag |> byte
            member __.ReadSByte tag = jsonReader.ReadPrimitiveAs (omitTag ()) tag |> sbyte
            member __.ReadInt16 tag = jsonReader.ReadPrimitiveAs (omitTag ()) tag |> int16
            member __.ReadInt32 tag = jsonReader.ReadPrimitiveAs (omitTag ()) tag |> int
            member __.ReadInt64 tag = jsonReader.ReadPrimitiveAs (omitTag ()) tag
            member __.ReadUInt16 tag = jsonReader.ReadPrimitiveAs (omitTag ()) tag |> uint16
            member __.ReadUInt32 tag = jsonReader.ReadPrimitiveAs (omitTag ()) tag |> uint32
            member __.ReadUInt64 tag = jsonReader.ReadPrimitiveAs (omitTag ()) tag |> uint64
            member __.ReadSingle tag =
                if not <| omitTag () then
                    jsonReader.ReadProperty tag
                    jsonReader.MoveNext()
                let value =
                    match jsonReader.TokenType with
                    | JsonToken.Float -> jsonReader.ValueAs () |> single
                    | JsonToken.String -> Single.Parse(jsonReader.ValueAs(), CultureInfo.InvariantCulture)
                    | _ -> raise <| new FormatException("not a float.")
                jsonReader.Read() |> ignore
                value
                
            member __.ReadDouble tag =
                if not <| omitTag () then
                    jsonReader.ReadProperty tag
                    jsonReader.MoveNext()
                let value =
                    match jsonReader.TokenType with
                    | JsonToken.Float -> jsonReader.ValueAs ()
                    | JsonToken.String -> Double.Parse(jsonReader.ValueAs(), CultureInfo.InvariantCulture)
                    | _ -> raise <| new FormatException("not a float.")
                jsonReader.Read() |> ignore
                value
            member __.ReadChar tag = let value = jsonReader.ReadPrimitiveAs (omitTag ()) tag in value.[0]
            member __.ReadString tag = jsonReader.ReadPrimitiveAs (omitTag ()) tag
            member __.ReadBigInteger tag = jsonReader.ReadPrimitiveAs (omitTag ()) tag |> BigInteger.Parse
            member __.ReadGuid tag = 
                if isBsonReader then 
                    jsonReader.ReadPrimitiveAs (omitTag ()) tag
                else
                    jsonReader.ReadPrimitiveAs (omitTag ()) tag |> Guid.Parse
            member __.ReadTimeSpan tag = jsonReader.ReadPrimitiveAs (omitTag ()) tag |> TimeSpan.Parse
            member __.ReadDecimal tag = jsonReader.ReadPrimitiveAs (omitTag ()) tag |> decimal
            // BSON spec mandates the use of Unix time; 
            // this has millisecond precision which results in loss of accuracy w.r.t. ticks
            // since the goal of FsPickler is to offer faithful representations of .NET objects
            // we choose to override the spec and serialize ticks outright.
            // see also https://json.codeplex.com/discussions/212067 
            member __.ReadDate tag = 
                if isBsonReader then
                    let ticks = jsonReader.ReadPrimitiveAs (omitTag ()) tag
                    DateTime(ticks)
                else
                    jsonReader.ReadPrimitiveAs (omitTag ()) tag
            member __.ReadBytes tag =
                if not <| omitTag () then
                    jsonReader.ReadProperty tag
                    jsonReader.Read() |> ignore
                let bytes =
                    if jsonReader.TokenType = JsonToken.Null then null
                    elif isBsonReader then jsonReader.ValueAs ()
                    else
                        let base64 = jsonReader.ValueAs ()
                        Convert.FromBase64String base64
                jsonReader.Read() |> ignore
                bytes
            member __.IsPrimitiveArraySerializationSupported = false
            member __.ReadPrimitiveArray _ _ = raise <| new NotImplementedException()
            member __.Dispose () = (jsonReader :> IDisposable).Dispose()