/*
 * Copyright (C) 2018 Square, Inc.
 *
 * 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.
 */
package com.squareup.moshi.recipes;

import com.squareup.moshi.FromJson;
import com.squareup.moshi.JsonAdapter;
import com.squareup.moshi.JsonDataException;
import com.squareup.moshi.JsonQualifier;
import com.squareup.moshi.JsonReader;
import com.squareup.moshi.JsonWriter;
import com.squareup.moshi.Moshi;
import com.squareup.moshi.ToJson;
import com.squareup.moshi.recipes.models.Card;
import com.squareup.moshi.recipes.models.Suit;
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

public final class MultipleFormats {
  public void run() throws Exception {
    Moshi moshi = new Moshi.Builder()
        .add(new MultipleFormatsCardAdapter())
        .add(new CardStringAdapter())
        .build();

    JsonAdapter<Card> cardAdapter = moshi.adapter(Card.class);

    // Decode cards from one format or the other.
    System.out.println(cardAdapter.fromJson("\"5D\""));
    System.out.println(cardAdapter.fromJson("{\"suit\": \"SPADES\", \"rank\": 5}"));

    // Cards are always encoded as strings.
    System.out.println(cardAdapter.toJson(new Card('5', Suit.CLUBS)));
  }

  /** Handles cards either as strings "5D" or as objects {"suit": "SPADES", "rank": 5}. */
  public final class MultipleFormatsCardAdapter {
    @ToJson void toJson(JsonWriter writer, Card value,
        @CardString JsonAdapter<Card> stringAdapter) throws IOException {
      stringAdapter.toJson(writer, value);
    }

    @FromJson Card fromJson(JsonReader reader, @CardString JsonAdapter<Card> stringAdapter,
        JsonAdapter<Card> defaultAdapter) throws IOException {
      if (reader.peek() == JsonReader.Token.STRING) {
        return stringAdapter.fromJson(reader);
      } else {
        return defaultAdapter.fromJson(reader);
      }
    }
  }

  /** Handles cards as strings only. */
  public final class CardStringAdapter {
    @ToJson String toJson(@CardString Card card) {
      return card.rank + card.suit.name().substring(0, 1);
    }

    @FromJson @CardString Card fromJson(String card) {
      if (card.length() != 2) throw new JsonDataException("Unknown card: " + card);

      char rank = card.charAt(0);
      switch (card.charAt(1)) {
        case 'C': return new Card(rank, Suit.CLUBS);
        case 'D': return new Card(rank, Suit.DIAMONDS);
        case 'H': return new Card(rank, Suit.HEARTS);
        case 'S': return new Card(rank, Suit.SPADES);
        default: throw new JsonDataException("unknown suit: " + card);
      }
    }
  }

  @Retention(RetentionPolicy.RUNTIME)
  @JsonQualifier
  @interface CardString {
  }

  public static void main(String[] args) throws Exception {
    new MultipleFormats().run();
  }
}